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    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
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.client;
19  
20  import static org.junit.Assert.assertTrue;
21  import static org.junit.Assert.fail;
22  
23  import java.io.IOException;
24  import java.net.SocketTimeoutException;
25  import java.util.Comparator;
26  import java.util.HashMap;
27  import java.util.Map;
28  import java.util.Random;
29  import java.util.SortedMap;
30  import java.util.concurrent.ConcurrentSkipListMap;
31  import java.util.concurrent.ExecutorService;
32  import java.util.concurrent.Executors;
33  import java.util.concurrent.atomic.AtomicInteger;
34  import java.util.concurrent.atomic.AtomicLong;
35  
36  import org.apache.hadoop.hbase.util.ByteStringer;
37  import org.apache.commons.lang.NotImplementedException;
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  import org.apache.hadoop.conf.Configuration;
41  import org.apache.hadoop.conf.Configured;
42  import org.apache.hadoop.hbase.DoNotRetryIOException;
43  import org.apache.hadoop.hbase.HBaseConfiguration;
44  import org.apache.hadoop.hbase.HConstants;
45  import org.apache.hadoop.hbase.HRegionInfo;
46  import org.apache.hadoop.hbase.HRegionLocation;
47  import org.apache.hadoop.hbase.RegionLocations;
48  import org.apache.hadoop.hbase.KeyValue;
49  import org.apache.hadoop.hbase.RegionTooBusyException;
50  import org.apache.hadoop.hbase.ServerName;
51  import org.apache.hadoop.hbase.testclassification.SmallTests;
52  import org.apache.hadoop.hbase.TableName;
53  import org.apache.hadoop.hbase.protobuf.generated.CellProtos;
54  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos;
55  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.BulkLoadHFileRequest;
56  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.BulkLoadHFileResponse;
57  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ClientService;
58  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ClientService.BlockingInterface;
59  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.CoprocessorServiceRequest;
60  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.CoprocessorServiceResponse;
61  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.GetRequest;
62  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.GetResponse;
63  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MultiRequest;
64  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MultiResponse;
65  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutateRequest;
66  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutateResponse;
67  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.RegionAction;
68  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.RegionActionResult;
69  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ResultOrException;
70  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ScanRequest;
71  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ScanResponse;
72  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.RegionSpecifier.RegionSpecifierType;
73  import org.apache.hadoop.hbase.regionserver.RegionServerStoppedException;
74  import org.apache.hadoop.hbase.security.User;
75  import org.apache.hadoop.hbase.util.Bytes;
76  import org.apache.hadoop.hbase.util.Pair;
77  import org.apache.hadoop.hbase.util.Threads;
78  import org.apache.hadoop.util.Tool;
79  import org.apache.hadoop.util.ToolRunner;
80  import org.junit.Before;
81  import org.junit.Ignore;
82  import org.junit.Test;
83  import org.junit.experimental.categories.Category;
84  import org.mockito.Mockito;
85  
86  import com.google.common.base.Stopwatch;
87  import com.google.protobuf.ByteString;
88  import com.google.protobuf.RpcController;
89  import com.google.protobuf.ServiceException;
90  
91  /**
92   * Test client behavior w/o setting up a cluster.
93   * Mock up cluster emissions.
94   */
95  @Category(SmallTests.class)
96  public class TestClientNoCluster extends Configured implements Tool {
97    private static final Log LOG = LogFactory.getLog(TestClientNoCluster.class);
98    private Configuration conf;
99    public static final ServerName META_SERVERNAME =
100       ServerName.valueOf("meta.example.org", 16010, 12345);
101 
102   @Before
103   public void setUp() throws Exception {
104     this.conf = HBaseConfiguration.create();
105     // Run my HConnection overrides.  Use my little HConnectionImplementation below which
106     // allows me insert mocks and also use my Registry below rather than the default zk based
107     // one so tests run faster and don't have zk dependency.
108     this.conf.set("hbase.client.registry.impl", SimpleConnectionRegistry.class.getName());
109   }
110 
111   /**
112    * Simple cluster registry inserted in place of our usual zookeeper based one.
113    */
114   static class SimpleConnectionRegistry implements ConnectionRegistry {
115     final ServerName META_HOST = META_SERVERNAME;
116 
117     @Override
118     public void init(Connection connection) {
119     }
120 
121     @Override
122     public ServerName getActiveMaster() {
123       return null;
124     }
125 
126     @Override
127     public RegionLocations getMetaRegionLocations() throws IOException {
128       return new RegionLocations(
129         new HRegionLocation(HRegionInfo.FIRST_META_REGIONINFO, META_HOST));
130     }
131 
132     @Override
133     public String getClusterId() {
134       return HConstants.CLUSTER_ID_DEFAULT;
135     }
136 
137     @Override
138     public boolean isTableOnlineState(TableName tableName, boolean enabled)
139     throws IOException {
140       return enabled;
141     }
142 
143     @Override
144     public int getCurrentNrHRS() throws IOException {
145       return 1;
146     }
147 
148     @Override
149     public void close() {
150     }
151   }
152 
153   /**
154    * Remove the @Ignore to try out timeout and retry asettings
155    * @throws IOException
156    */
157   @Ignore
158   @Test
159   public void testTimeoutAndRetries() throws IOException {
160     Configuration localConfig = HBaseConfiguration.create(this.conf);
161     // This override mocks up our exists/get call to throw a RegionServerStoppedException.
162     localConfig.set("hbase.client.connection.impl", RpcTimeoutConnection.class.getName());
163     Table table = new HTable(localConfig, TableName.META_TABLE_NAME);
164     Throwable t = null;
165     LOG.info("Start");
166     try {
167       // An exists call turns into a get w/ a flag.
168       table.exists(new Get(Bytes.toBytes("abc")));
169     } catch (SocketTimeoutException e) {
170       // I expect this exception.
171       LOG.info("Got expected exception", e);
172       t = e;
173     } catch (RetriesExhaustedException e) {
174       // This is the old, unwanted behavior.  If we get here FAIL!!!
175       fail();
176     } finally {
177       table.close();
178     }
179     LOG.info("Stop");
180     assertTrue(t != null);
181   }
182 
183   /**
184    * Test that operation timeout prevails over rpc default timeout and retries, etc.
185    * @throws IOException
186    */
187   @Test
188   public void testRpcTimeout() throws IOException {
189     Configuration localConfig = HBaseConfiguration.create(this.conf);
190     // This override mocks up our exists/get call to throw a RegionServerStoppedException.
191     localConfig.set("hbase.client.connection.impl", RpcTimeoutConnection.class.getName());
192     int pause = 10;
193     localConfig.setInt("hbase.client.pause", pause);
194     localConfig.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 10);
195     // Set the operation timeout to be < the pause.  Expectation is that after first pause, we will
196     // fail out of the rpc because the rpc timeout will have been set to the operation tiemout
197     // and it has expired.  Otherwise, if this functionality is broke, all retries will be run --
198     // all ten of them -- and we'll get the RetriesExhaustedException exception.
199     localConfig.setInt(HConstants.HBASE_CLIENT_META_OPERATION_TIMEOUT, pause - 1);
200     Table table = new HTable(localConfig, TableName.META_TABLE_NAME);
201     Throwable t = null;
202     try {
203       // An exists call turns into a get w/ a flag.
204       table.exists(new Get(Bytes.toBytes("abc")));
205     } catch (SocketTimeoutException e) {
206       // I expect this exception.
207       LOG.info("Got expected exception", e);
208       t = e;
209     } catch (RetriesExhaustedException e) {
210       // This is the old, unwanted behavior.  If we get here FAIL!!!
211       fail();
212     } finally {
213       table.close();
214     }
215     assertTrue(t != null);
216   }
217 
218   @Test
219   public void testDoNotRetryMetaScanner() throws IOException {
220     this.conf.set("hbase.client.connection.impl",
221       RegionServerStoppedOnScannerOpenConnection.class.getName());
222     try (Connection connection = ConnectionFactory.createConnection(conf)) {
223       MetaScanner.metaScan(connection, null);
224     }
225   }
226 
227   @Test
228   public void testDoNotRetryOnScanNext() throws IOException {
229     this.conf.set("hbase.client.connection.impl",
230       RegionServerStoppedOnScannerOpenConnection.class.getName());
231     // Go against meta else we will try to find first region for the table on construction which
232     // means we'll have to do a bunch more mocking.  Tests that go against meta only should be
233     // good for a bit of testing.
234     Table table = new HTable(this.conf, TableName.META_TABLE_NAME);
235     ResultScanner scanner = table.getScanner(HConstants.CATALOG_FAMILY);
236     try {
237       Result result = null;
238       while ((result = scanner.next()) != null) {
239         LOG.info(result);
240       }
241     } finally {
242       scanner.close();
243       table.close();
244     }
245   }
246 
247   @Test
248   public void testRegionServerStoppedOnScannerOpen() throws IOException {
249     this.conf.set("hbase.client.connection.impl",
250       RegionServerStoppedOnScannerOpenConnection.class.getName());
251     // Go against meta else we will try to find first region for the table on construction which
252     // means we'll have to do a bunch more mocking.  Tests that go against meta only should be
253     // good for a bit of testing.
254     Table table = new HTable(this.conf, TableName.META_TABLE_NAME);
255     ResultScanner scanner = table.getScanner(HConstants.CATALOG_FAMILY);
256     try {
257       Result result = null;
258       while ((result = scanner.next()) != null) {
259         LOG.info(result);
260       }
261     } finally {
262       scanner.close();
263       table.close();
264     }
265   }
266 
267   /**
268    * Override to shutdown going to zookeeper for cluster id and meta location.
269    */
270   static class ScanOpenNextThenExceptionThenRecoverConnection
271   extends ConnectionManager.HConnectionImplementation {
272     final ClientService.BlockingInterface stub;
273 
274     ScanOpenNextThenExceptionThenRecoverConnection(Configuration conf,
275         boolean managed, ExecutorService pool) throws IOException {
276       super(conf, managed);
277       // Mock up my stub so open scanner returns a scanner id and then on next, we throw
278       // exceptions for three times and then after that, we return no more to scan.
279       this.stub = Mockito.mock(ClientService.BlockingInterface.class);
280       long sid = 12345L;
281       try {
282         Mockito.when(stub.scan((RpcController)Mockito.any(),
283             (ClientProtos.ScanRequest)Mockito.any())).
284           thenReturn(ClientProtos.ScanResponse.newBuilder().setScannerId(sid).build()).
285           thenThrow(new ServiceException(new RegionServerStoppedException("From Mockito"))).
286           thenReturn(ClientProtos.ScanResponse.newBuilder().setScannerId(sid).
287               setMoreResults(false).build());
288       } catch (ServiceException e) {
289         throw new IOException(e);
290       }
291     }
292 
293     @Override
294     public BlockingInterface getClient(ServerName sn) throws IOException {
295       return this.stub;
296     }
297   }
298 
299   @Test
300   public void testConnectionClosedOnRegionLocate() throws IOException {
301     Configuration testConf = new Configuration(this.conf);
302     testConf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 2);
303     // Go against meta else we will try to find first region for the table on construction which
304     // means we'll have to do a bunch more mocking. Tests that go against meta only should be
305     // good for a bit of testing.
306     Connection connection = ConnectionFactory.createConnection(testConf);
307     Table table = connection.getTable(TableName.META_TABLE_NAME);
308     connection.close();
309     try {
310       Get get = new Get(Bytes.toBytes("dummyRow"));
311       table.get(get);
312       fail("Should have thrown DoNotRetryException but no exception thrown");
313     } catch (Exception e) {
314       if (!(e instanceof DoNotRetryIOException)) {
315         String errMsg =
316             "Should have thrown DoNotRetryException but actually " + e.getClass().getSimpleName();
317         LOG.error(errMsg, e);
318         fail(errMsg);
319       }
320     } finally {
321       table.close();
322     }
323   }
324 
325   /**
326    * Override to shutdown going to zookeeper for cluster id and meta location.
327    */
328   static class RegionServerStoppedOnScannerOpenConnection
329   extends ConnectionManager.HConnectionImplementation {
330     final ClientService.BlockingInterface stub;
331 
332     RegionServerStoppedOnScannerOpenConnection(Configuration conf, boolean managed,
333         ExecutorService pool, User user) throws IOException {
334       super(conf, managed);
335       // Mock up my stub so open scanner returns a scanner id and then on next, we throw
336       // exceptions for three times and then after that, we return no more to scan.
337       this.stub = Mockito.mock(ClientService.BlockingInterface.class);
338       long sid = 12345L;
339       try {
340         Mockito.when(stub.scan((RpcController)Mockito.any(),
341             (ClientProtos.ScanRequest)Mockito.any())).
342           thenReturn(ClientProtos.ScanResponse.newBuilder().setScannerId(sid).build()).
343           thenThrow(new ServiceException(new RegionServerStoppedException("From Mockito"))).
344           thenReturn(ClientProtos.ScanResponse.newBuilder().setScannerId(sid).
345               setMoreResults(false).build());
346       } catch (ServiceException e) {
347         throw new IOException(e);
348       }
349     }
350 
351     @Override
352     public BlockingInterface getClient(ServerName sn) throws IOException {
353       return this.stub;
354     }
355   }
356 
357   /**
358    * Override to check we are setting rpc timeout right.
359    */
360   static class RpcTimeoutConnection
361   extends ConnectionManager.HConnectionImplementation {
362     final ClientService.BlockingInterface stub;
363 
364     RpcTimeoutConnection(Configuration conf, boolean managed, ExecutorService pool, User user)
365     throws IOException {
366       super(conf, managed);
367       // Mock up my stub so an exists call -- which turns into a get -- throws an exception
368       this.stub = Mockito.mock(ClientService.BlockingInterface.class);
369       try {
370         Mockito.when(stub.get((RpcController)Mockito.any(),
371             (ClientProtos.GetRequest)Mockito.any())).
372           thenThrow(new ServiceException(new RegionServerStoppedException("From Mockito")));
373       } catch (ServiceException e) {
374         throw new IOException(e);
375       }
376     }
377 
378     @Override
379     public BlockingInterface getClient(ServerName sn) throws IOException {
380       return this.stub;
381     }
382   }
383 
384   /**
385    * Fake many regionservers and many regions on a connection implementation.
386    */
387   static class ManyServersManyRegionsConnection
388   extends ConnectionManager.HConnectionImplementation {
389     // All access should be synchronized
390     final Map<ServerName, ClientService.BlockingInterface> serversByClient;
391 
392     /**
393      * Map of faked-up rows of a 'meta table'.
394      */
395     final SortedMap<byte [], Pair<HRegionInfo, ServerName>> meta;
396     final AtomicLong sequenceids = new AtomicLong(0);
397     private final Configuration conf;
398 
399     ManyServersManyRegionsConnection(Configuration conf, boolean managed,
400         ExecutorService pool, User user)
401     throws IOException {
402       super(conf, managed, pool, user);
403       int serverCount = conf.getInt("hbase.test.servers", 10);
404       this.serversByClient =
405         new HashMap<ServerName, ClientService.BlockingInterface>(serverCount);
406       this.meta = makeMeta(Bytes.toBytes(
407         conf.get("hbase.test.tablename", Bytes.toString(BIG_USER_TABLE))),
408         conf.getInt("hbase.test.regions", 100),
409         conf.getLong("hbase.test.namespace.span", 1000),
410         serverCount);
411       this.conf = conf;
412     }
413 
414     @Override
415     public ClientService.BlockingInterface getClient(ServerName sn) throws IOException {
416       // if (!sn.toString().startsWith("meta")) LOG.info(sn);
417       ClientService.BlockingInterface stub = null;
418       synchronized (this.serversByClient) {
419         stub = this.serversByClient.get(sn);
420         if (stub == null) {
421           stub = new FakeServer(this.conf, meta, sequenceids);
422           this.serversByClient.put(sn, stub);
423         }
424       }
425       return stub;
426     }
427   }
428 
429   static MultiResponse doMultiResponse(final SortedMap<byte [], Pair<HRegionInfo, ServerName>> meta,
430       final AtomicLong sequenceids, final MultiRequest request) {
431     // Make a response to match the request.  Act like there were no failures.
432     ClientProtos.MultiResponse.Builder builder = ClientProtos.MultiResponse.newBuilder();
433     // Per Region.
434     RegionActionResult.Builder regionActionResultBuilder =
435         RegionActionResult.newBuilder();
436     ResultOrException.Builder roeBuilder = ResultOrException.newBuilder();
437     for (RegionAction regionAction: request.getRegionActionList()) {
438       regionActionResultBuilder.clear();
439       // Per Action in a Region.
440       for (ClientProtos.Action action: regionAction.getActionList()) {
441         roeBuilder.clear();
442         // Return empty Result and proper index as result.
443         roeBuilder.setResult(ClientProtos.Result.getDefaultInstance());
444         roeBuilder.setIndex(action.getIndex());
445         regionActionResultBuilder.addResultOrException(roeBuilder.build());
446       }
447       builder.addRegionActionResult(regionActionResultBuilder.build());
448     }
449     return builder.build();
450   }
451 
452   /**
453    * Fake 'server'.
454    * Implements the ClientService responding as though it were a 'server' (presumes a new
455    * ClientService.BlockingInterface made per server).
456    */
457   static class FakeServer implements ClientService.BlockingInterface {
458     private AtomicInteger multiInvocationsCount = new AtomicInteger(0);
459     private final SortedMap<byte [], Pair<HRegionInfo, ServerName>> meta;
460     private final AtomicLong sequenceids;
461     private final long multiPause;
462     private final int tooManyMultiRequests;
463 
464     FakeServer(final Configuration c, final SortedMap<byte [], Pair<HRegionInfo, ServerName>> meta,
465         final AtomicLong sequenceids) {
466       this.meta = meta;
467       this.sequenceids = sequenceids;
468 
469       // Pause to simulate the server taking time applying the edits.  This will drive up the
470       // number of threads used over in client.
471       this.multiPause = c.getLong("hbase.test.multi.pause.when.done", 0);
472       this.tooManyMultiRequests = c.getInt("hbase.test.multi.too.many", 3);
473     }
474 
475     @Override
476     public GetResponse get(RpcController controller, GetRequest request)
477     throws ServiceException {
478       boolean metaRegion = isMetaRegion(request.getRegion().getValue().toByteArray(),
479         request.getRegion().getType());
480       if (!metaRegion) {
481         return doGetResponse(request);
482       }
483       return doMetaGetResponse(meta, request);
484     }
485 
486     private GetResponse doGetResponse(GetRequest request) {
487       ClientProtos.Result.Builder resultBuilder = ClientProtos.Result.newBuilder();
488       ByteString row = request.getGet().getRow();
489       resultBuilder.addCell(getStartCode(row));
490       GetResponse.Builder builder = GetResponse.newBuilder();
491       builder.setResult(resultBuilder.build());
492       return builder.build();
493     }
494 
495     @Override
496     public MutateResponse mutate(RpcController controller,
497         MutateRequest request) throws ServiceException {
498       throw new NotImplementedException();
499     }
500 
501     @Override
502     public ScanResponse scan(RpcController controller,
503         ScanRequest request) throws ServiceException {
504       // Presume it is a scan of meta for now. Not all scans provide a region spec expecting
505       // the server to keep reference by scannerid.  TODO.
506       return doMetaScanResponse(meta, sequenceids, request);
507     }
508 
509     @Override
510     public BulkLoadHFileResponse bulkLoadHFile(
511         RpcController controller, BulkLoadHFileRequest request)
512         throws ServiceException {
513       throw new NotImplementedException();
514     }
515 
516     @Override
517     public CoprocessorServiceResponse execService(
518         RpcController controller, CoprocessorServiceRequest request)
519         throws ServiceException {
520       throw new NotImplementedException();
521     }
522 
523     @Override
524     public MultiResponse multi(RpcController controller, MultiRequest request)
525     throws ServiceException {
526       int concurrentInvocations = this.multiInvocationsCount.incrementAndGet();
527       try {
528         if (concurrentInvocations >= tooManyMultiRequests) {
529           throw new ServiceException(new RegionTooBusyException("concurrentInvocations=" +
530            concurrentInvocations));
531         }
532         Threads.sleep(multiPause);
533         return doMultiResponse(meta, sequenceids, request);
534       } finally {
535         this.multiInvocationsCount.decrementAndGet();
536       }
537     }
538 
539     @Override
540     public CoprocessorServiceResponse execRegionServerService(RpcController controller,
541         CoprocessorServiceRequest request) throws ServiceException {
542       throw new NotImplementedException();
543     }
544   }
545 
546   static ScanResponse doMetaScanResponse(final SortedMap<byte [], Pair<HRegionInfo, ServerName>> meta,
547       final AtomicLong sequenceids, final ScanRequest request) {
548     ScanResponse.Builder builder = ScanResponse.newBuilder();
549     int max = request.getNumberOfRows();
550     int count = 0;
551     Map<byte [], Pair<HRegionInfo, ServerName>> tail =
552       request.hasScan()? meta.tailMap(request.getScan().getStartRow().toByteArray()): meta;
553       ClientProtos.Result.Builder resultBuilder = ClientProtos.Result.newBuilder();
554     for (Map.Entry<byte [], Pair<HRegionInfo, ServerName>> e: tail.entrySet()) {
555       // Can be 0 on open of a scanner -- i.e. rpc to setup scannerid only.
556       if (max <= 0) break;
557       if (++count > max) break;
558       HRegionInfo hri = e.getValue().getFirst();
559       ByteString row = ByteStringer.wrap(hri.getRegionName());
560       resultBuilder.clear();
561       resultBuilder.addCell(getRegionInfo(row, hri));
562       resultBuilder.addCell(getServer(row, e.getValue().getSecond()));
563       resultBuilder.addCell(getStartCode(row));
564       builder.addResults(resultBuilder.build());
565       // Set more to false if we are on the last region in table.
566       if (hri.getEndKey().length <= 0) builder.setMoreResults(false);
567       else builder.setMoreResults(true);
568     }
569     // If no scannerid, set one.
570     builder.setScannerId(request.hasScannerId()?
571       request.getScannerId(): sequenceids.incrementAndGet());
572     return builder.build();
573   }
574 
575   static GetResponse doMetaGetResponse(final SortedMap<byte [], Pair<HRegionInfo, ServerName>> meta,
576       final GetRequest request) {
577     ClientProtos.Result.Builder resultBuilder = ClientProtos.Result.newBuilder();
578     ByteString row = request.getGet().getRow();
579     Pair<HRegionInfo, ServerName> p = meta.get(row.toByteArray());
580     if (p == null) {
581       if (request.getGet().getClosestRowBefore()) {
582         byte [] bytes = row.toByteArray();
583         SortedMap<byte [], Pair<HRegionInfo, ServerName>> head =
584           bytes != null? meta.headMap(bytes): meta;
585         p = head == null? null: head.get(head.lastKey());
586       }
587     }
588     if (p != null) {
589       resultBuilder.addCell(getRegionInfo(row, p.getFirst()));
590       resultBuilder.addCell(getServer(row, p.getSecond()));
591     }
592     resultBuilder.addCell(getStartCode(row));
593     GetResponse.Builder builder = GetResponse.newBuilder();
594     builder.setResult(resultBuilder.build());
595     return builder.build();
596   }
597 
598   /**
599    * @param name region name or encoded region name.
600    * @param type
601    * @return True if we are dealing with a hbase:meta region.
602    */
603   static boolean isMetaRegion(final byte [] name, final RegionSpecifierType type) {
604     switch (type) {
605     case REGION_NAME:
606       return Bytes.equals(HRegionInfo.FIRST_META_REGIONINFO.getRegionName(), name);
607     case ENCODED_REGION_NAME:
608       return Bytes.equals(HRegionInfo.FIRST_META_REGIONINFO.getEncodedNameAsBytes(), name);
609     default: throw new UnsupportedOperationException();
610     }
611   }
612 
613   private final static ByteString CATALOG_FAMILY_BYTESTRING =
614       ByteStringer.wrap(HConstants.CATALOG_FAMILY);
615   private final static ByteString REGIONINFO_QUALIFIER_BYTESTRING =
616       ByteStringer.wrap(HConstants.REGIONINFO_QUALIFIER);
617   private final static ByteString SERVER_QUALIFIER_BYTESTRING =
618       ByteStringer.wrap(HConstants.SERVER_QUALIFIER);
619 
620   static CellProtos.Cell.Builder getBaseCellBuilder(final ByteString row) {
621     CellProtos.Cell.Builder cellBuilder = CellProtos.Cell.newBuilder();
622     cellBuilder.setRow(row);
623     cellBuilder.setFamily(CATALOG_FAMILY_BYTESTRING);
624     cellBuilder.setTimestamp(System.currentTimeMillis());
625     return cellBuilder;
626   }
627 
628   static CellProtos.Cell getRegionInfo(final ByteString row, final HRegionInfo hri) {
629     CellProtos.Cell.Builder cellBuilder = getBaseCellBuilder(row);
630     cellBuilder.setQualifier(REGIONINFO_QUALIFIER_BYTESTRING);
631     cellBuilder.setValue(ByteStringer.wrap(hri.toByteArray()));
632     return cellBuilder.build();
633   }
634 
635   static CellProtos.Cell getServer(final ByteString row, final ServerName sn) {
636     CellProtos.Cell.Builder cellBuilder = getBaseCellBuilder(row);
637     cellBuilder.setQualifier(SERVER_QUALIFIER_BYTESTRING);
638     cellBuilder.setValue(ByteString.copyFromUtf8(sn.getHostAndPort()));
639     return cellBuilder.build();
640   }
641 
642   static CellProtos.Cell getStartCode(final ByteString row) {
643     CellProtos.Cell.Builder cellBuilder = getBaseCellBuilder(row);
644     cellBuilder.setQualifier(ByteStringer.wrap(HConstants.STARTCODE_QUALIFIER));
645     // TODO:
646     cellBuilder.setValue(ByteStringer.wrap(Bytes.toBytes(META_SERVERNAME.getStartcode())));
647     return cellBuilder.build();
648   }
649 
650   private static final byte [] BIG_USER_TABLE = Bytes.toBytes("t");
651 
652   /**
653    * Format passed integer.  Zero-pad.
654    * Copied from hbase-server PE class and small amendment.  Make them share.
655    * @param number
656    * @return Returns zero-prefixed 10-byte wide decimal version of passed
657    * number (Does absolute in case number is negative).
658    */
659   private static byte [] format(final long number) {
660     byte [] b = new byte[10];
661     long d = number;
662     for (int i = b.length - 1; i >= 0; i--) {
663       b[i] = (byte)((d % 10) + '0');
664       d /= 10;
665     }
666     return b;
667   }
668 
669   /**
670    * @param count
671    * @param namespaceSpan
672    * @return <code>count</code> regions
673    */
674   private static HRegionInfo [] makeHRegionInfos(final byte [] tableName, final int count,
675       final long namespaceSpan) {
676     byte [] startKey = HConstants.EMPTY_BYTE_ARRAY;
677     byte [] endKey = HConstants.EMPTY_BYTE_ARRAY;
678     long interval = namespaceSpan / count;
679     HRegionInfo [] hris = new HRegionInfo[count];
680     for (int i = 0; i < count; i++) {
681       if (i == 0) {
682         endKey = format(interval);
683       } else {
684         startKey = endKey;
685         if (i == count - 1) endKey = HConstants.EMPTY_BYTE_ARRAY;
686         else endKey = format((i + 1) * interval);
687       }
688       hris[i] = new HRegionInfo(TableName.valueOf(tableName), startKey, endKey);
689     }
690     return hris;
691   }
692 
693   /**
694    * @param count
695    * @return Return <code>count</code> servernames.
696    */
697   private static ServerName [] makeServerNames(final int count) {
698     ServerName [] sns = new ServerName[count];
699     for (int i = 0; i < count; i++) {
700       sns[i] = ServerName.valueOf("" + i + ".example.org", 16010, i);
701     }
702     return sns;
703   }
704 
705   /**
706    * Comparator for meta row keys.
707    */
708   private static class MetaRowsComparator implements Comparator<byte []> {
709     private final KeyValue.KVComparator delegate = new KeyValue.MetaComparator();
710     @Override
711     public int compare(byte[] left, byte[] right) {
712       return delegate.compareRows(left, 0, left.length, right, 0, right.length);
713     }
714   }
715 
716   /**
717    * Create up a map that is keyed by meta row name and whose value is the HRegionInfo and
718    * ServerName to return for this row.
719    * @return Map with faked hbase:meta content in it.
720    */
721   static SortedMap<byte [], Pair<HRegionInfo, ServerName>> makeMeta(final byte [] tableName,
722       final int regionCount, final long namespaceSpan, final int serverCount) {
723     // I need a comparator for meta rows so we sort properly.
724     SortedMap<byte [], Pair<HRegionInfo, ServerName>> meta =
725       new ConcurrentSkipListMap<byte[], Pair<HRegionInfo,ServerName>>(new MetaRowsComparator());
726     HRegionInfo [] hris = makeHRegionInfos(tableName, regionCount, namespaceSpan);
727     ServerName [] serverNames = makeServerNames(serverCount);
728     int per = regionCount / serverCount;
729     int count = 0;
730     for (HRegionInfo hri: hris) {
731       Pair<HRegionInfo, ServerName> p =
732         new Pair<HRegionInfo, ServerName>(hri, serverNames[count++ / per]);
733       meta.put(hri.getRegionName(), p);
734     }
735     return meta;
736   }
737 
738   /**
739    * Code for each 'client' to run.
740    *
741    * @param id
742    * @param c
743    * @param sharedConnection
744    * @throws IOException
745    */
746   static void cycle(int id, final Configuration c, final Connection sharedConnection) throws IOException {
747     long namespaceSpan = c.getLong("hbase.test.namespace.span", 1000000);
748     long startTime = System.currentTimeMillis();
749     final int printInterval = 100000;
750     Random rd = new Random(id);
751     boolean get = c.getBoolean("hbase.test.do.gets", false);
752     TableName tableName = TableName.valueOf(BIG_USER_TABLE);
753     if (get) {
754       try (Table table = sharedConnection.getTable(tableName)){
755         Stopwatch stopWatch = new Stopwatch();
756         stopWatch.start();
757         for (int i = 0; i < namespaceSpan; i++) {
758           byte [] b = format(rd.nextLong());
759           Get g = new Get(b);
760           table.get(g);
761           if (i % printInterval == 0) {
762             LOG.info("Get " + printInterval + "/" + stopWatch.elapsedMillis());
763             stopWatch.reset();
764             stopWatch.start();
765           }
766         }
767         LOG.info("Finished a cycle putting " + namespaceSpan + " in " +
768             (System.currentTimeMillis() - startTime) + "ms");
769       }
770     } else {
771       try (BufferedMutator mutator = sharedConnection.getBufferedMutator(tableName)) {
772         Stopwatch stopWatch = new Stopwatch();
773         stopWatch.start();
774         for (int i = 0; i < namespaceSpan; i++) {
775           byte [] b = format(rd.nextLong());
776           Put p = new Put(b);
777           p.add(HConstants.CATALOG_FAMILY, b, b);
778           mutator.mutate(p);
779           if (i % printInterval == 0) {
780             LOG.info("Put " + printInterval + "/" + stopWatch.elapsedMillis());
781             stopWatch.reset();
782             stopWatch.start();
783           }
784         }
785         LOG.info("Finished a cycle putting " + namespaceSpan + " in " +
786             (System.currentTimeMillis() - startTime) + "ms");
787         }
788     }
789   }
790 
791   @Override
792   public int run(String[] arg0) throws Exception {
793     int errCode = 0;
794     // TODO: Make command options.
795     // How many servers to fake.
796     final int servers = 1;
797     // How many regions to put on the faked servers.
798     final int regions = 100000;
799     // How many 'keys' in the faked regions.
800     final long namespaceSpan = 50000000;
801     // How long to take to pause after doing a put; make this long if you want to fake a struggling
802     // server.
803     final long multiPause = 0;
804     // Check args make basic sense.
805     if ((namespaceSpan < regions) || (regions < servers)) {
806       throw new IllegalArgumentException("namespaceSpan=" + namespaceSpan + " must be > regions=" +
807         regions + " which must be > servers=" + servers);
808     }
809 
810     // Set my many servers and many regions faking connection in place.
811     getConf().set("hbase.client.connection.impl",
812       ManyServersManyRegionsConnection.class.getName());
813     // Use simple kv registry rather than zk
814     getConf().set("hbase.client.registry.impl", SimpleConnectionRegistry.class.getName());
815     // When to report fails.  Default is we report the 10th.  This means we'll see log everytime
816     // an exception is thrown -- usually RegionTooBusyException when we have more than
817     // hbase.test.multi.too.many requests outstanding at any time.
818     getConf().setInt("hbase.client.start.log.errors.counter", 0);
819 
820     // Ugly but this is only way to pass in configs.into ManyServersManyRegionsConnection class.
821     getConf().setInt("hbase.test.regions", regions);
822     getConf().setLong("hbase.test.namespace.span", namespaceSpan);
823     getConf().setLong("hbase.test.servers", servers);
824     getConf().set("hbase.test.tablename", Bytes.toString(BIG_USER_TABLE));
825     getConf().setLong("hbase.test.multi.pause.when.done", multiPause);
826     // Let there be ten outstanding requests at a time before we throw RegionBusyException.
827     getConf().setInt("hbase.test.multi.too.many", 10);
828     final int clients = 2;
829 
830     // Have them all share the same connection so they all share the same instance of
831     // ManyServersManyRegionsConnection so I can keep an eye on how many requests by server.
832     final ExecutorService pool = Executors.newCachedThreadPool(Threads.getNamedThreadFactory("p"));
833       // Executors.newFixedThreadPool(servers * 10, Threads.getNamedThreadFactory("p"));
834     // Share a connection so I can keep counts in the 'server' on concurrency.
835     final Connection sharedConnection = ConnectionFactory.createConnection(getConf()/*, pool*/);
836     try {
837       Thread [] ts = new Thread[clients];
838       for (int j = 0; j < ts.length; j++) {
839         final int id = j;
840         ts[j] = new Thread("" + j) {
841           final Configuration c = getConf();
842 
843           @Override
844           public void run() {
845             try {
846               cycle(id, c, sharedConnection);
847             } catch (IOException e) {
848               e.printStackTrace();
849             }
850           }
851         };
852         ts[j].start();
853       }
854       for (int j = 0; j < ts.length; j++) {
855         ts[j].join();
856       }
857     } finally {
858       sharedConnection.close();
859     }
860     return errCode;
861   }
862 
863   /**
864    * Run a client instance against a faked up server.
865    * @param args TODO
866    * @throws Exception
867    */
868   public static void main(String[] args) throws Exception {
869     System.exit(ToolRunner.run(HBaseConfiguration.create(), new TestClientNoCluster(), args));
870   }
871 }