|
17 | 17 | */ |
18 | 18 | package org.apache.hadoop.hbase.client; |
19 | 19 |
|
| 20 | +import static org.apache.hadoop.hbase.HConstants.HIGH_QOS; |
20 | 21 | import static org.apache.hadoop.hbase.HConstants.NORMAL_QOS; |
21 | 22 | import static org.apache.hadoop.hbase.HConstants.SYSTEMTABLE_QOS; |
22 | 23 | import static org.apache.hadoop.hbase.NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR; |
| 24 | +import static org.junit.Assert.assertEquals; |
| 25 | +import static org.junit.Assert.assertFalse; |
23 | 26 | import static org.junit.Assert.assertNotNull; |
| 27 | +import static org.junit.Assert.assertTrue; |
24 | 28 | import static org.mockito.ArgumentMatchers.any; |
25 | 29 | import static org.mockito.ArgumentMatchers.anyInt; |
26 | 30 | import static org.mockito.ArgumentMatchers.anyLong; |
|
33 | 37 |
|
34 | 38 | import java.io.IOException; |
35 | 39 | import java.util.Arrays; |
| 40 | +import java.util.Optional; |
36 | 41 | import java.util.concurrent.CompletableFuture; |
| 42 | +import java.util.concurrent.ExecutorService; |
| 43 | +import java.util.concurrent.Executors; |
| 44 | +import java.util.concurrent.TimeUnit; |
37 | 45 | import java.util.concurrent.atomic.AtomicInteger; |
| 46 | + |
38 | 47 | import org.apache.hadoop.conf.Configuration; |
39 | 48 | import org.apache.hadoop.hbase.Cell; |
40 | 49 | import org.apache.hadoop.hbase.Cell.Type; |
|
59 | 68 | import org.mockito.ArgumentMatcher; |
60 | 69 | import org.mockito.invocation.InvocationOnMock; |
61 | 70 | import org.mockito.stubbing.Answer; |
62 | | - |
63 | 71 | import org.apache.hbase.thirdparty.com.google.protobuf.RpcCallback; |
64 | | - |
65 | 72 | import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; |
66 | 73 | import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos; |
67 | 74 | import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.ClientService; |
@@ -91,41 +98,18 @@ public class TestAsyncTableRpcPriority { |
91 | 98 |
|
92 | 99 | private ClientService.Interface stub; |
93 | 100 |
|
| 101 | + private ExecutorService threadPool; |
| 102 | + |
94 | 103 | private AsyncConnection conn; |
95 | 104 |
|
96 | 105 | @Rule |
97 | 106 | public TestName name = new TestName(); |
98 | 107 |
|
99 | 108 | @Before |
100 | 109 | public void setUp() throws IOException { |
| 110 | + this.threadPool = Executors.newSingleThreadExecutor(); |
101 | 111 | stub = mock(ClientService.Interface.class); |
102 | | - AtomicInteger scanNextCalled = new AtomicInteger(0); |
103 | | - doAnswer(new Answer<Void>() { |
104 | 112 |
|
105 | | - @Override |
106 | | - public Void answer(InvocationOnMock invocation) throws Throwable { |
107 | | - ScanRequest req = invocation.getArgument(1); |
108 | | - RpcCallback<ScanResponse> done = invocation.getArgument(2); |
109 | | - if (!req.hasScannerId()) { |
110 | | - done.run(ScanResponse.newBuilder().setScannerId(1).setTtl(800) |
111 | | - .setMoreResultsInRegion(true).setMoreResults(true).build()); |
112 | | - } else { |
113 | | - if (req.hasCloseScanner() && req.getCloseScanner()) { |
114 | | - done.run(ScanResponse.getDefaultInstance()); |
115 | | - } else { |
116 | | - Cell cell = CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY).setType(Type.Put) |
117 | | - .setRow(Bytes.toBytes(scanNextCalled.incrementAndGet())) |
118 | | - .setFamily(Bytes.toBytes("cf")).setQualifier(Bytes.toBytes("cq")) |
119 | | - .setValue(Bytes.toBytes("v")).build(); |
120 | | - Result result = Result.create(Arrays.asList(cell)); |
121 | | - done.run( |
122 | | - ScanResponse.newBuilder().setScannerId(1).setTtl(800).setMoreResultsInRegion(true) |
123 | | - .setMoreResults(true).addResults(ProtobufUtil.toResult(result)).build()); |
124 | | - } |
125 | | - } |
126 | | - return null; |
127 | | - } |
128 | | - }).when(stub).scan(any(HBaseRpcController.class), any(ScanRequest.class), any()); |
129 | 113 | doAnswer(new Answer<Void>() { |
130 | 114 |
|
131 | 115 | @Override |
@@ -218,6 +202,16 @@ public boolean matches(HBaseRpcController controller) { |
218 | 202 | }); |
219 | 203 | } |
220 | 204 |
|
| 205 | + private ScanRequest assertScannerCloseRequest() { |
| 206 | + return argThat(new ArgumentMatcher<ScanRequest>() { |
| 207 | + |
| 208 | + @Override |
| 209 | + public boolean matches(ScanRequest request) { |
| 210 | + return request.hasCloseScanner() && request.getCloseScanner(); |
| 211 | + } |
| 212 | + }); |
| 213 | + } |
| 214 | + |
221 | 215 | @Test |
222 | 216 | public void testGet() { |
223 | 217 | conn.getTable(TableName.valueOf(name.getMethodName())) |
@@ -478,53 +472,113 @@ public void testCheckAndMutateMetaTable() throws IOException { |
478 | 472 | any(ClientProtos.MultiRequest.class), any()); |
479 | 473 | } |
480 | 474 |
|
| 475 | + private CompletableFuture<Void> mockScanReturnRenewFuture(int scanPriority) { |
| 476 | + int scannerId = 1; |
| 477 | + CompletableFuture<Void> future = new CompletableFuture<>(); |
| 478 | + AtomicInteger scanNextCalled = new AtomicInteger(0); |
| 479 | + doAnswer(new Answer<Void>() { |
| 480 | + |
| 481 | + @SuppressWarnings("FutureReturnValueIgnored") |
| 482 | + @Override |
| 483 | + public Void answer(InvocationOnMock invocation) throws Throwable { |
| 484 | + threadPool.submit(() -> { |
| 485 | + ScanRequest req = invocation.getArgument(1); |
| 486 | + RpcCallback<ScanResponse> done = invocation.getArgument(2); |
| 487 | + if (!req.hasScannerId()) { |
| 488 | + done.run(ScanResponse.newBuilder() |
| 489 | + .setScannerId(scannerId).setTtl(800) |
| 490 | + .setMoreResultsInRegion(true).setMoreResults(true) |
| 491 | + .build()); |
| 492 | + } else { |
| 493 | + if (req.hasRenew() && req.getRenew()) { |
| 494 | + future.complete(null); |
| 495 | + } |
| 496 | + |
| 497 | + assertFalse("close scanner should not come in with scan priority " + scanPriority, |
| 498 | + req.hasCloseScanner() && req.getCloseScanner()); |
| 499 | + |
| 500 | + Cell cell = CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY) |
| 501 | + .setType(Type.Put).setRow(Bytes.toBytes(scanNextCalled.incrementAndGet())) |
| 502 | + .setFamily(Bytes.toBytes("cf")).setQualifier(Bytes.toBytes("cq")) |
| 503 | + .setValue(Bytes.toBytes("v")).build(); |
| 504 | + Result result = Result.create(Arrays.asList(cell)); |
| 505 | + done.run( |
| 506 | + ScanResponse.newBuilder() |
| 507 | + .setScannerId(scannerId).setTtl(800).setMoreResultsInRegion(true) |
| 508 | + .setMoreResults(true).addResults(ProtobufUtil.toResult(result)) |
| 509 | + .build()); |
| 510 | + } |
| 511 | + }); |
| 512 | + return null; |
| 513 | + } |
| 514 | + }).when(stub).scan(assertPriority(scanPriority), any(ScanRequest.class), any()); |
| 515 | + |
| 516 | + doAnswer(new Answer<Void>() { |
| 517 | + |
| 518 | + @SuppressWarnings("FutureReturnValueIgnored") |
| 519 | + @Override |
| 520 | + public Void answer(InvocationOnMock invocation) throws Throwable { |
| 521 | + threadPool.submit(() ->{ |
| 522 | + ScanRequest req = invocation.getArgument(1); |
| 523 | + RpcCallback<ScanResponse> done = invocation.getArgument(2); |
| 524 | + assertTrue("close request should have scannerId", req.hasScannerId()); |
| 525 | + assertEquals("close request's scannerId should match", scannerId, req.getScannerId()); |
| 526 | + assertTrue("close request should have closerScanner set", |
| 527 | + req.hasCloseScanner() && req.getCloseScanner()); |
| 528 | + |
| 529 | + done.run(ScanResponse.getDefaultInstance()); |
| 530 | + }); |
| 531 | + return null; |
| 532 | + } |
| 533 | + }).when(stub).scan(assertPriority(HIGH_QOS), assertScannerCloseRequest(), any()); |
| 534 | + return future; |
| 535 | + } |
| 536 | + |
481 | 537 | @Test |
482 | | - public void testScan() throws IOException, InterruptedException { |
483 | | - try (ResultScanner scanner = conn.getTable(TableName.valueOf(name.getMethodName())) |
484 | | - .getScanner(new Scan().setCaching(1).setMaxResultSize(1).setPriority(19))) { |
485 | | - assertNotNull(scanner.next()); |
486 | | - Thread.sleep(1000); |
487 | | - } |
488 | | - Thread.sleep(1000); |
489 | | - // open, next, several renew lease, and then close |
490 | | - verify(stub, atLeast(4)).scan(assertPriority(19), any(ScanRequest.class), any()); |
| 538 | + public void testScan() throws Exception { |
| 539 | + CompletableFuture<Void> renewFuture = mockScanReturnRenewFuture(19); |
| 540 | + testForTable(TableName.valueOf(name.getMethodName()), renewFuture, Optional.of(19)); |
491 | 541 | } |
492 | 542 |
|
493 | 543 | @Test |
494 | | - public void testScanNormalTable() throws IOException, InterruptedException { |
495 | | - try (ResultScanner scanner = conn.getTable(TableName.valueOf(name.getMethodName())) |
496 | | - .getScanner(new Scan().setCaching(1).setMaxResultSize(1))) { |
497 | | - assertNotNull(scanner.next()); |
498 | | - Thread.sleep(1000); |
499 | | - } |
500 | | - Thread.sleep(1000); |
501 | | - // open, next, several renew lease, and then close |
502 | | - verify(stub, atLeast(4)).scan(assertPriority(NORMAL_QOS), any(ScanRequest.class), any()); |
| 544 | + public void testScanNormalTable() throws Exception { |
| 545 | + CompletableFuture<Void> renewFuture = mockScanReturnRenewFuture(NORMAL_QOS); |
| 546 | + testForTable(TableName.valueOf(name.getMethodName()), renewFuture, Optional.of(NORMAL_QOS)); |
503 | 547 | } |
504 | 548 |
|
505 | 549 | @Test |
506 | | - public void testScanSystemTable() throws IOException, InterruptedException { |
507 | | - try (ResultScanner scanner = |
508 | | - conn.getTable(TableName.valueOf(SYSTEM_NAMESPACE_NAME_STR, name.getMethodName())) |
509 | | - .getScanner(new Scan().setCaching(1).setMaxResultSize(1))) { |
510 | | - assertNotNull(scanner.next()); |
511 | | - Thread.sleep(1000); |
512 | | - } |
513 | | - Thread.sleep(1000); |
514 | | - // open, next, several renew lease, and then close |
515 | | - verify(stub, atLeast(4)).scan(assertPriority(SYSTEMTABLE_QOS), any(ScanRequest.class), any()); |
| 550 | + public void testScanSystemTable() throws Exception { |
| 551 | + CompletableFuture<Void> renewFuture = mockScanReturnRenewFuture(SYSTEMTABLE_QOS); |
| 552 | + testForTable(TableName.valueOf(SYSTEM_NAMESPACE_NAME_STR, name.getMethodName()), |
| 553 | + renewFuture, Optional.empty()); |
516 | 554 | } |
517 | 555 |
|
518 | 556 | @Test |
519 | | - public void testScanMetaTable() throws IOException, InterruptedException { |
520 | | - try (ResultScanner scanner = conn.getTable(TableName.META_TABLE_NAME) |
521 | | - .getScanner(new Scan().setCaching(1).setMaxResultSize(1))) { |
| 557 | + public void testScanMetaTable() throws Exception { |
| 558 | + CompletableFuture<Void> renewFuture = mockScanReturnRenewFuture(SYSTEMTABLE_QOS); |
| 559 | + testForTable(TableName.META_TABLE_NAME, renewFuture, Optional.empty()); |
| 560 | + } |
| 561 | + |
| 562 | + private void testForTable(TableName tableName, CompletableFuture<Void> renewFuture, |
| 563 | + Optional<Integer> priority) throws Exception { |
| 564 | + Scan scan = new Scan().setCaching(1).setMaxResultSize(1); |
| 565 | + priority.ifPresent(scan::setPriority); |
| 566 | + |
| 567 | + try (ResultScanner scanner = conn.getTable(tableName).getScanner(scan)) { |
522 | 568 | assertNotNull(scanner.next()); |
523 | | - Thread.sleep(1000); |
| 569 | + // wait for at least one renew to come in before closing |
| 570 | + renewFuture.join(); |
524 | 571 | } |
525 | | - Thread.sleep(1000); |
526 | | - // open, next, several renew lease, and then close |
527 | | - verify(stub, atLeast(4)).scan(assertPriority(SYSTEMTABLE_QOS), any(ScanRequest.class), any()); |
| 572 | + |
| 573 | + // ensures the close thread has time to finish before asserting |
| 574 | + threadPool.shutdown(); |
| 575 | + threadPool.awaitTermination(5, TimeUnit.SECONDS); |
| 576 | + |
| 577 | + // just verify that the calls happened. verification of priority occurred in the mocking |
| 578 | + // open, next, then one or more lease renewals, then close |
| 579 | + verify(stub, atLeast(4)).scan(any(), any(ScanRequest.class), any()); |
| 580 | + // additionally, explicitly check for a close request |
| 581 | + verify(stub, times(1)).scan(any(), assertScannerCloseRequest(), any()); |
528 | 582 | } |
529 | 583 |
|
530 | 584 | @Test |
|
0 commit comments