001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.client;
019
020import static org.junit.Assert.assertArrayEquals;
021import static org.junit.Assert.assertEquals;
022import static org.junit.Assert.assertFalse;
023import static org.junit.Assert.assertNull;
024import static org.junit.Assert.assertTrue;
025
026import java.io.IOException;
027import java.io.UncheckedIOException;
028import java.util.Arrays;
029import java.util.List;
030import java.util.concurrent.ArrayBlockingQueue;
031import java.util.concurrent.BlockingQueue;
032import java.util.concurrent.CountDownLatch;
033import java.util.concurrent.ExecutionException;
034import java.util.concurrent.ForkJoinPool;
035import java.util.concurrent.atomic.AtomicInteger;
036import java.util.concurrent.atomic.AtomicLong;
037import java.util.function.Supplier;
038import java.util.stream.IntStream;
039import org.apache.commons.io.IOUtils;
040import org.apache.hadoop.hbase.HBaseClassTestRule;
041import org.apache.hadoop.hbase.HBaseTestingUtility;
042import org.apache.hadoop.hbase.TableName;
043import org.apache.hadoop.hbase.io.TimeRange;
044import org.apache.hadoop.hbase.testclassification.ClientTests;
045import org.apache.hadoop.hbase.testclassification.MediumTests;
046import org.apache.hadoop.hbase.util.Bytes;
047import org.apache.hadoop.hbase.util.Pair;
048import org.junit.AfterClass;
049import org.junit.Before;
050import org.junit.BeforeClass;
051import org.junit.ClassRule;
052import org.junit.Rule;
053import org.junit.Test;
054import org.junit.experimental.categories.Category;
055import org.junit.rules.TestName;
056import org.junit.runner.RunWith;
057import org.junit.runners.Parameterized;
058import org.junit.runners.Parameterized.Parameter;
059import org.junit.runners.Parameterized.Parameters;
060
061@RunWith(Parameterized.class)
062@Category({ MediumTests.class, ClientTests.class })
063public class TestAsyncTable {
064
065  @ClassRule
066  public static final HBaseClassTestRule CLASS_RULE =
067      HBaseClassTestRule.forClass(TestAsyncTable.class);
068
069  private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
070
071  private static TableName TABLE_NAME = TableName.valueOf("async");
072
073  private static byte[] FAMILY = Bytes.toBytes("cf");
074
075  private static byte[] QUALIFIER = Bytes.toBytes("cq");
076
077  private static byte[] VALUE = Bytes.toBytes("value");
078
079  private static AsyncConnection ASYNC_CONN;
080
081  @Rule
082  public TestName testName = new TestName();
083
084  private byte[] row;
085
086  @Parameter
087  public Supplier<AsyncTable<?>> getTable;
088
089  private static AsyncTable<?> getRawTable() {
090    return ASYNC_CONN.getTable(TABLE_NAME);
091  }
092
093  private static AsyncTable<?> getTable() {
094    return ASYNC_CONN.getTable(TABLE_NAME, ForkJoinPool.commonPool());
095  }
096
097  @Parameters
098  public static List<Object[]> params() {
099    return Arrays.asList(new Supplier<?>[] { TestAsyncTable::getRawTable },
100      new Supplier<?>[] { TestAsyncTable::getTable });
101  }
102
103  @BeforeClass
104  public static void setUpBeforeClass() throws Exception {
105    TEST_UTIL.startMiniCluster(1);
106    TEST_UTIL.createTable(TABLE_NAME, FAMILY);
107    TEST_UTIL.waitTableAvailable(TABLE_NAME);
108    ASYNC_CONN = ConnectionFactory.createAsyncConnection(TEST_UTIL.getConfiguration()).get();
109  }
110
111  @AfterClass
112  public static void tearDownAfterClass() throws Exception {
113    IOUtils.closeQuietly(ASYNC_CONN);
114    TEST_UTIL.shutdownMiniCluster();
115  }
116
117  @Before
118  public void setUp() throws IOException, InterruptedException {
119    row = Bytes.toBytes(testName.getMethodName().replaceAll("[^0-9A-Za-z]", "_"));
120  }
121
122  @Test
123  public void testSimple() throws Exception {
124    AsyncTable<?> table = getTable.get();
125    table.put(new Put(row).addColumn(FAMILY, QUALIFIER, VALUE)).get();
126    assertTrue(table.exists(new Get(row).addColumn(FAMILY, QUALIFIER)).get());
127    Result result = table.get(new Get(row).addColumn(FAMILY, QUALIFIER)).get();
128    assertArrayEquals(VALUE, result.getValue(FAMILY, QUALIFIER));
129    table.delete(new Delete(row)).get();
130    result = table.get(new Get(row).addColumn(FAMILY, QUALIFIER)).get();
131    assertTrue(result.isEmpty());
132    assertFalse(table.exists(new Get(row).addColumn(FAMILY, QUALIFIER)).get());
133  }
134
135  private byte[] concat(byte[] base, int index) {
136    return Bytes.toBytes(Bytes.toString(base) + "-" + index);
137  }
138
139  @Test
140  public void testSimpleMultiple() throws Exception {
141    AsyncTable<?> table = getTable.get();
142    int count = 100;
143    CountDownLatch putLatch = new CountDownLatch(count);
144    IntStream.range(0, count).forEach(
145      i -> table.put(new Put(concat(row, i)).addColumn(FAMILY, QUALIFIER, concat(VALUE, i)))
146          .thenAccept(x -> putLatch.countDown()));
147    putLatch.await();
148    BlockingQueue<Boolean> existsResp = new ArrayBlockingQueue<>(count);
149    IntStream.range(0, count)
150        .forEach(i -> table.exists(new Get(concat(row, i)).addColumn(FAMILY, QUALIFIER))
151            .thenAccept(x -> existsResp.add(x)));
152    for (int i = 0; i < count; i++) {
153      assertTrue(existsResp.take());
154    }
155    BlockingQueue<Pair<Integer, Result>> getResp = new ArrayBlockingQueue<>(count);
156    IntStream.range(0, count)
157        .forEach(i -> table.get(new Get(concat(row, i)).addColumn(FAMILY, QUALIFIER))
158            .thenAccept(x -> getResp.add(Pair.newPair(i, x))));
159    for (int i = 0; i < count; i++) {
160      Pair<Integer, Result> pair = getResp.take();
161      assertArrayEquals(concat(VALUE, pair.getFirst()),
162        pair.getSecond().getValue(FAMILY, QUALIFIER));
163    }
164    CountDownLatch deleteLatch = new CountDownLatch(count);
165    IntStream.range(0, count).forEach(
166      i -> table.delete(new Delete(concat(row, i))).thenAccept(x -> deleteLatch.countDown()));
167    deleteLatch.await();
168    IntStream.range(0, count)
169        .forEach(i -> table.exists(new Get(concat(row, i)).addColumn(FAMILY, QUALIFIER))
170            .thenAccept(x -> existsResp.add(x)));
171    for (int i = 0; i < count; i++) {
172      assertFalse(existsResp.take());
173    }
174    IntStream.range(0, count)
175        .forEach(i -> table.get(new Get(concat(row, i)).addColumn(FAMILY, QUALIFIER))
176            .thenAccept(x -> getResp.add(Pair.newPair(i, x))));
177    for (int i = 0; i < count; i++) {
178      Pair<Integer, Result> pair = getResp.take();
179      assertTrue(pair.getSecond().isEmpty());
180    }
181  }
182
183  @Test
184  public void testIncrement() throws InterruptedException, ExecutionException {
185    AsyncTable<?> table = getTable.get();
186    int count = 100;
187    CountDownLatch latch = new CountDownLatch(count);
188    AtomicLong sum = new AtomicLong(0L);
189    IntStream.range(0, count)
190        .forEach(i -> table.incrementColumnValue(row, FAMILY, QUALIFIER, 1).thenAccept(x -> {
191          sum.addAndGet(x);
192          latch.countDown();
193        }));
194    latch.await();
195    assertEquals(count, Bytes.toLong(
196      table.get(new Get(row).addColumn(FAMILY, QUALIFIER)).get().getValue(FAMILY, QUALIFIER)));
197    assertEquals((1 + count) * count / 2, sum.get());
198  }
199
200  @Test
201  public void testAppend() throws InterruptedException, ExecutionException {
202    AsyncTable<?> table = getTable.get();
203    int count = 10;
204    CountDownLatch latch = new CountDownLatch(count);
205    char suffix = ':';
206    AtomicLong suffixCount = new AtomicLong(0L);
207    IntStream.range(0, count).forEachOrdered(
208      i -> table.append(new Append(row).addColumn(FAMILY, QUALIFIER, Bytes.toBytes("" + i + suffix)))
209          .thenAccept(r -> {
210            suffixCount.addAndGet(Bytes.toString(r.getValue(FAMILY, QUALIFIER)).chars()
211                .filter(x -> x == suffix).count());
212            latch.countDown();
213          }));
214    latch.await();
215    assertEquals((1 + count) * count / 2, suffixCount.get());
216    String value = Bytes.toString(
217      table.get(new Get(row).addColumn(FAMILY, QUALIFIER)).get().getValue(FAMILY, QUALIFIER));
218    int[] actual = Arrays.asList(value.split("" + suffix)).stream().mapToInt(Integer::parseInt)
219        .sorted().toArray();
220    assertArrayEquals(IntStream.range(0, count).toArray(), actual);
221  }
222
223  @Test
224  public void testCheckAndPut() throws InterruptedException, ExecutionException {
225    AsyncTable<?> table = getTable.get();
226    AtomicInteger successCount = new AtomicInteger(0);
227    AtomicInteger successIndex = new AtomicInteger(-1);
228    int count = 10;
229    CountDownLatch latch = new CountDownLatch(count);
230    IntStream.range(0, count)
231        .forEach(i -> table.checkAndMutate(row, FAMILY).qualifier(QUALIFIER).ifNotExists()
232            .thenPut(new Put(row).addColumn(FAMILY, QUALIFIER, concat(VALUE, i))).thenAccept(x -> {
233              if (x) {
234                successCount.incrementAndGet();
235                successIndex.set(i);
236              }
237              latch.countDown();
238            }));
239    latch.await();
240    assertEquals(1, successCount.get());
241    String actual = Bytes.toString(table.get(new Get(row)).get().getValue(FAMILY, QUALIFIER));
242    assertTrue(actual.endsWith(Integer.toString(successIndex.get())));
243  }
244
245  @Test
246  public void testCheckAndDelete() throws InterruptedException, ExecutionException {
247    AsyncTable<?> table = getTable.get();
248    int count = 10;
249    CountDownLatch putLatch = new CountDownLatch(count + 1);
250    table.put(new Put(row).addColumn(FAMILY, QUALIFIER, VALUE)).thenRun(() -> putLatch.countDown());
251    IntStream.range(0, count)
252        .forEach(i -> table.put(new Put(row).addColumn(FAMILY, concat(QUALIFIER, i), VALUE))
253            .thenRun(() -> putLatch.countDown()));
254    putLatch.await();
255
256    AtomicInteger successCount = new AtomicInteger(0);
257    AtomicInteger successIndex = new AtomicInteger(-1);
258    CountDownLatch deleteLatch = new CountDownLatch(count);
259    IntStream.range(0, count)
260        .forEach(i -> table.checkAndMutate(row, FAMILY).qualifier(QUALIFIER).ifEquals(VALUE)
261            .thenDelete(
262              new Delete(row).addColumn(FAMILY, QUALIFIER).addColumn(FAMILY, concat(QUALIFIER, i)))
263            .thenAccept(x -> {
264              if (x) {
265                successCount.incrementAndGet();
266                successIndex.set(i);
267              }
268              deleteLatch.countDown();
269            }));
270    deleteLatch.await();
271    assertEquals(1, successCount.get());
272    Result result = table.get(new Get(row)).get();
273    IntStream.range(0, count).forEach(i -> {
274      if (i == successIndex.get()) {
275        assertFalse(result.containsColumn(FAMILY, concat(QUALIFIER, i)));
276      } else {
277        assertArrayEquals(VALUE, result.getValue(FAMILY, concat(QUALIFIER, i)));
278      }
279    });
280  }
281
282  @Test
283  public void testMutateRow() throws InterruptedException, ExecutionException, IOException {
284    AsyncTable<?> table = getTable.get();
285    RowMutations mutation = new RowMutations(row);
286    mutation.add(new Put(row).addColumn(FAMILY, concat(QUALIFIER, 1), VALUE));
287    table.mutateRow(mutation).get();
288    Result result = table.get(new Get(row)).get();
289    assertArrayEquals(VALUE, result.getValue(FAMILY, concat(QUALIFIER, 1)));
290
291    mutation = new RowMutations(row);
292    mutation.add(new Delete(row).addColumn(FAMILY, concat(QUALIFIER, 1)));
293    mutation.add(new Put(row).addColumn(FAMILY, concat(QUALIFIER, 2), VALUE));
294    table.mutateRow(mutation).get();
295    result = table.get(new Get(row)).get();
296    assertNull(result.getValue(FAMILY, concat(QUALIFIER, 1)));
297    assertArrayEquals(VALUE, result.getValue(FAMILY, concat(QUALIFIER, 2)));
298  }
299
300  @Test
301  public void testCheckAndMutate() throws InterruptedException, ExecutionException {
302    AsyncTable<?> table = getTable.get();
303    int count = 10;
304    CountDownLatch putLatch = new CountDownLatch(count + 1);
305    table.put(new Put(row).addColumn(FAMILY, QUALIFIER, VALUE)).thenRun(() -> putLatch.countDown());
306    IntStream.range(0, count)
307        .forEach(i -> table.put(new Put(row).addColumn(FAMILY, concat(QUALIFIER, i), VALUE))
308            .thenRun(() -> putLatch.countDown()));
309    putLatch.await();
310
311    AtomicInteger successCount = new AtomicInteger(0);
312    AtomicInteger successIndex = new AtomicInteger(-1);
313    CountDownLatch mutateLatch = new CountDownLatch(count);
314    IntStream.range(0, count).forEach(i -> {
315      RowMutations mutation = new RowMutations(row);
316      try {
317        mutation.add(new Delete(row).addColumn(FAMILY, QUALIFIER));
318        mutation.add(new Put(row).addColumn(FAMILY, concat(QUALIFIER, i), concat(VALUE, i)));
319      } catch (IOException e) {
320        throw new UncheckedIOException(e);
321      }
322      table.checkAndMutate(row, FAMILY).qualifier(QUALIFIER).ifEquals(VALUE).thenMutate(mutation)
323          .thenAccept(x -> {
324            if (x) {
325              successCount.incrementAndGet();
326              successIndex.set(i);
327            }
328            mutateLatch.countDown();
329          });
330    });
331    mutateLatch.await();
332    assertEquals(1, successCount.get());
333    Result result = table.get(new Get(row)).get();
334    IntStream.range(0, count).forEach(i -> {
335      if (i == successIndex.get()) {
336        assertArrayEquals(concat(VALUE, i), result.getValue(FAMILY, concat(QUALIFIER, i)));
337      } else {
338        assertArrayEquals(VALUE, result.getValue(FAMILY, concat(QUALIFIER, i)));
339      }
340    });
341  }
342
343  @Test
344  public void testCheckAndMutateWithTimeRange() throws Exception {
345    AsyncTable<?> table = getTable.get();
346    final long ts = System.currentTimeMillis() / 2;
347    Put put = new Put(row);
348    put.addColumn(FAMILY, QUALIFIER, ts, VALUE);
349
350    boolean ok = table.checkAndMutate(row, FAMILY).qualifier(QUALIFIER)
351      .ifNotExists()
352      .thenPut(put)
353      .get();
354    assertTrue(ok);
355
356    ok = table.checkAndMutate(row, FAMILY).qualifier(QUALIFIER)
357      .timeRange(TimeRange.at(ts + 10000))
358      .ifEquals(VALUE)
359      .thenPut(put)
360      .get();
361    assertFalse(ok);
362
363    ok = table.checkAndMutate(row, FAMILY).qualifier(QUALIFIER)
364      .timeRange(TimeRange.at(ts))
365      .ifEquals(VALUE)
366      .thenPut(put)
367      .get();
368    assertTrue(ok);
369
370    RowMutations rm = new RowMutations(row)
371      .add((Mutation) put);
372    ok = table.checkAndMutate(row, FAMILY).qualifier(QUALIFIER)
373      .timeRange(TimeRange.at(ts + 10000))
374      .ifEquals(VALUE)
375      .thenMutate(rm)
376      .get();
377    assertFalse(ok);
378
379    ok = table.checkAndMutate(row, FAMILY).qualifier(QUALIFIER)
380      .timeRange(TimeRange.at(ts))
381      .ifEquals(VALUE)
382      .thenMutate(rm)
383      .get();
384    assertTrue(ok);
385
386    Delete delete = new Delete(row)
387      .addColumn(FAMILY, QUALIFIER);
388
389    ok = table.checkAndMutate(row, FAMILY).qualifier(QUALIFIER)
390      .timeRange(TimeRange.at(ts + 10000))
391      .ifEquals(VALUE)
392      .thenDelete(delete)
393      .get();
394    assertFalse(ok);
395
396    ok = table.checkAndMutate(row, FAMILY).qualifier(QUALIFIER)
397      .timeRange(TimeRange.at(ts))
398      .ifEquals(VALUE)
399      .thenDelete(delete)
400      .get();
401    assertTrue(ok);
402  }
403}