View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.client;
20  
21  import java.io.IOException;
22  import java.util.Arrays;
23  import java.util.Collection;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.apache.hadoop.conf.Configuration;
28  import org.apache.hadoop.hbase.Cell;
29  import org.apache.hadoop.hbase.DoNotRetryIOException;
30  import org.apache.hadoop.hbase.HBaseTestingUtility;
31  import org.apache.hadoop.hbase.TableName;
32  import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
33  import org.apache.hadoop.hbase.coprocessor.MultiRowMutationEndpoint;
34  import org.apache.hadoop.hbase.regionserver.HRegion;
35  import org.apache.hadoop.hbase.testclassification.LargeTests;
36  import org.apache.hadoop.hbase.util.Bytes;
37  import org.junit.After;
38  import org.junit.Before;
39  import org.junit.BeforeClass;
40  import org.junit.Rule;
41  import org.junit.Test;
42  import org.junit.experimental.categories.Category;
43  import org.junit.rules.TestName;
44  import org.junit.runner.RunWith;
45  import org.junit.runners.Parameterized;
46  import org.junit.runners.Parameterized.Parameters;
47  
48  import static org.junit.Assert.assertEquals;
49  import static org.junit.Assert.fail;
50  import static org.junit.Assert.assertTrue;
51  
52  /**
53   * Run Increment tests that use the HBase clients; {@link HTable}.
54   * 
55   * Test is parameterized to run the slow and fast increment code paths. If fast, in the @before, we
56   * do a rolling restart of the single regionserver so that it can pick up the go fast configuration.
57   * Doing it this way should be faster than starting/stopping a cluster per test.
58   * 
59   * Test takes a long time because spin up a cluster between each run -- ugh.
60   */
61  @RunWith(Parameterized.class)
62  @Category(LargeTests.class)
63  @SuppressWarnings ("deprecation")
64  public class TestIncrementsFromClientSide {
65    final Log LOG = LogFactory.getLog(getClass());
66    protected final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
67    private static byte [] ROW = Bytes.toBytes("testRow");
68    private static byte [] FAMILY = Bytes.toBytes("testFamily");
69    // This test depends on there being only one slave running at at a time. See the @Before
70    // method where we do rolling restart.
71    protected static int SLAVES = 1;
72    private String oldINCREMENT_FAST_BUT_NARROW_CONSISTENCY_KEY;
73    @Rule public TestName name = new TestName();
74    @Parameters(name = "fast={0}")
75    public static Collection<Object []> data() {
76      return Arrays.asList(new Object[] {Boolean.FALSE}, new Object [] {Boolean.TRUE});
77    }
78    private final boolean fast;
79  
80    public TestIncrementsFromClientSide(final boolean fast) {
81      this.fast = fast;
82    }
83  
84    @BeforeClass
85    public static void beforeClass() throws Exception {
86      Configuration conf = TEST_UTIL.getConfiguration();
87      conf.setStrings(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,
88          MultiRowMutationEndpoint.class.getName());
89      conf.setBoolean("hbase.table.sanity.checks", true); // enable for below tests
90    }
91  
92    @Before
93    public void before() throws Exception {
94      Configuration conf = TEST_UTIL.getConfiguration();
95      if (this.fast) {
96        this.oldINCREMENT_FAST_BUT_NARROW_CONSISTENCY_KEY =
97            conf.get(HRegion.INCREMENT_FAST_BUT_NARROW_CONSISTENCY_KEY);
98        conf.setBoolean(HRegion.INCREMENT_FAST_BUT_NARROW_CONSISTENCY_KEY, this.fast);
99      }
100     // We need more than one region server in this test
101     TEST_UTIL.startMiniCluster(SLAVES);
102   }
103 
104   @After
105   public void after() throws Exception {
106     Configuration conf = TEST_UTIL.getConfiguration();
107     if (this.fast) {
108       if (this.oldINCREMENT_FAST_BUT_NARROW_CONSISTENCY_KEY != null) {
109         conf.set(HRegion.INCREMENT_FAST_BUT_NARROW_CONSISTENCY_KEY,
110             this.oldINCREMENT_FAST_BUT_NARROW_CONSISTENCY_KEY);
111       }
112     }
113     TEST_UTIL.shutdownMiniCluster();
114   }
115 
116   @Test
117   public void testIncrementWithDeletes() throws Exception {
118     LOG.info("Starting " + this.name.getMethodName());
119     final TableName TABLENAME =
120         TableName.valueOf(filterStringSoTableNameSafe(this.name.getMethodName()));
121     Table ht = TEST_UTIL.createTable(TABLENAME, FAMILY);
122     final byte[] COLUMN = Bytes.toBytes("column");
123 
124     ht.incrementColumnValue(ROW, FAMILY, COLUMN, 5);
125     TEST_UTIL.flush(TABLENAME);
126 
127     Delete del = new Delete(ROW);
128     ht.delete(del);
129 
130     ht.incrementColumnValue(ROW, FAMILY, COLUMN, 5);
131 
132     Get get = new Get(ROW);
133     if (this.fast) get.setIsolationLevel(IsolationLevel.READ_UNCOMMITTED);
134     Result r = ht.get(get);
135     assertEquals(1, r.size());
136     assertEquals(5, Bytes.toLong(r.getValue(FAMILY, COLUMN)));
137   }
138 
139   @Test
140   public void testIncrementingInvalidValue() throws Exception {
141     LOG.info("Starting " + this.name.getMethodName());
142     final TableName TABLENAME =
143         TableName.valueOf(filterStringSoTableNameSafe(this.name.getMethodName()));
144     Table ht = TEST_UTIL.createTable(TABLENAME, FAMILY);
145     final byte[] COLUMN = Bytes.toBytes("column");
146     Put p = new Put(ROW);
147     // write an integer here (not a Long)
148     p.add(FAMILY, COLUMN, Bytes.toBytes(5));
149     ht.put(p);
150     try {
151       ht.incrementColumnValue(ROW, FAMILY, COLUMN, 5);
152       fail("Should have thrown DoNotRetryIOException");
153     } catch (DoNotRetryIOException iox) {
154       // success
155     }
156     Increment inc = new Increment(ROW);
157     inc.addColumn(FAMILY, COLUMN, 5);
158     try {
159       ht.increment(inc);
160       fail("Should have thrown DoNotRetryIOException");
161     } catch (DoNotRetryIOException iox) {
162       // success
163     }
164   }
165 
166   @Test
167   public void testIncrementReturnValue() throws Exception {
168     LOG.info("Starting " + this.name.getMethodName());
169     final TableName TABLENAME =
170       TableName.valueOf(filterStringSoTableNameSafe(this.name.getMethodName()));
171     Table ht = TEST_UTIL.createTable(TABLENAME, FAMILY);
172     final byte[] COLUMN = Bytes.toBytes("column");
173     Put p = new Put(ROW);
174     p.add(FAMILY, COLUMN, Bytes.toBytes(5L));
175     ht.put(p);
176 
177     Increment inc = new Increment(ROW);
178     inc.addColumn(FAMILY, COLUMN, 5L);
179 
180     Result r = ht.increment(inc);
181     long result = Bytes.toLong(r.getValue(FAMILY, COLUMN));
182     assertEquals(10, result);
183 
184     if (this.fast) {
185       inc = new Increment(ROW);
186       inc.addColumn(FAMILY, COLUMN, 5L);
187       inc.setReturnResults(false);
188       r = ht.increment(inc);
189       assertTrue(r.getExists() == null);
190     }
191 
192   }
193 
194 
195   @Test
196   public void testIncrementInvalidArguments() throws Exception {
197     LOG.info("Starting " + this.name.getMethodName());
198     final TableName TABLENAME =
199         TableName.valueOf(filterStringSoTableNameSafe(this.name.getMethodName()));
200     Table ht = TEST_UTIL.createTable(TABLENAME, FAMILY);
201     final byte[] COLUMN = Bytes.toBytes("column");
202     try {
203       // try null row
204       ht.incrementColumnValue(null, FAMILY, COLUMN, 5);
205       fail("Should have thrown IOException");
206     } catch (IOException iox) {
207       // success
208     }
209     try {
210       // try null family
211       ht.incrementColumnValue(ROW, null, COLUMN, 5);
212       fail("Should have thrown IOException");
213     } catch (IOException iox) {
214       // success
215     }
216     try {
217       // try null qualifier
218       ht.incrementColumnValue(ROW, FAMILY, null, 5);
219       fail("Should have thrown IOException");
220     } catch (IOException iox) {
221       // success
222     }
223     // try null row
224     try {
225       Increment incNoRow = new Increment((byte [])null);
226       incNoRow.addColumn(FAMILY, COLUMN, 5);
227       fail("Should have thrown IllegalArgumentException");
228     } catch (IllegalArgumentException iax) {
229       // success
230     } catch (NullPointerException npe) {
231       // success
232     }
233     // try null family
234     try {
235       Increment incNoFamily = new Increment(ROW);
236       incNoFamily.addColumn(null, COLUMN, 5);
237       fail("Should have thrown IllegalArgumentException");
238     } catch (IllegalArgumentException iax) {
239       // success
240     }
241     // try null qualifier
242     try {
243       Increment incNoQualifier = new Increment(ROW);
244       incNoQualifier.addColumn(FAMILY, null, 5);
245       fail("Should have thrown IllegalArgumentException");
246     } catch (IllegalArgumentException iax) {
247       // success
248     }
249   }
250 
251   @Test
252   public void testIncrementOutOfOrder() throws Exception {
253     LOG.info("Starting " + this.name.getMethodName());
254     final TableName TABLENAME =
255         TableName.valueOf(filterStringSoTableNameSafe(this.name.getMethodName()));
256     Table ht = TEST_UTIL.createTable(TABLENAME, FAMILY);
257 
258     byte [][] QUALIFIERS = new byte [][] {
259       Bytes.toBytes("B"), Bytes.toBytes("A"), Bytes.toBytes("C")
260     };
261 
262     Increment inc = new Increment(ROW);
263     for (int i=0; i<QUALIFIERS.length; i++) {
264       inc.addColumn(FAMILY, QUALIFIERS[i], 1);
265     }
266     ht.increment(inc);
267 
268     // Verify expected results
269     Get get = new Get(ROW);
270     if (this.fast) get.setIsolationLevel(IsolationLevel.READ_UNCOMMITTED);
271     Result r = ht.get(get);
272     Cell [] kvs = r.rawCells();
273     assertEquals(3, kvs.length);
274     assertIncrementKey(kvs[0], ROW, FAMILY, QUALIFIERS[1], 1);
275     assertIncrementKey(kvs[1], ROW, FAMILY, QUALIFIERS[0], 1);
276     assertIncrementKey(kvs[2], ROW, FAMILY, QUALIFIERS[2], 1);
277 
278     // Now try multiple columns again
279     inc = new Increment(ROW);
280     for (int i=0; i<QUALIFIERS.length; i++) {
281       inc.addColumn(FAMILY, QUALIFIERS[i], 1);
282     }
283     ht.increment(inc);
284 
285     // Verify
286     r = ht.get(get);
287     kvs = r.rawCells();
288     assertEquals(3, kvs.length);
289     assertIncrementKey(kvs[0], ROW, FAMILY, QUALIFIERS[1], 2);
290     assertIncrementKey(kvs[1], ROW, FAMILY, QUALIFIERS[0], 2);
291     assertIncrementKey(kvs[2], ROW, FAMILY, QUALIFIERS[2], 2);
292   }
293 
294   @Test
295   public void testIncrementOnSameColumn() throws Exception {
296     LOG.info("Starting " + this.name.getMethodName());
297     final byte[] TABLENAME = Bytes.toBytes(filterStringSoTableNameSafe(this.name.getMethodName()));
298     HTable ht = TEST_UTIL.createTable(TABLENAME, FAMILY);
299 
300     byte[][] QUALIFIERS =
301         new byte[][] { Bytes.toBytes("A"), Bytes.toBytes("B"), Bytes.toBytes("C") };
302 
303     Increment inc = new Increment(ROW);
304     for (int i = 0; i < QUALIFIERS.length; i++) {
305       inc.addColumn(FAMILY, QUALIFIERS[i], 1);
306       inc.addColumn(FAMILY, QUALIFIERS[i], 1);
307     }
308     ht.increment(inc);
309 
310     // Verify expected results
311     Get get = new Get(ROW);
312     if (this.fast) get.setIsolationLevel(IsolationLevel.READ_UNCOMMITTED);
313     Result r = ht.get(get);
314     Cell[] kvs = r.rawCells();
315     assertEquals(3, kvs.length);
316     assertIncrementKey(kvs[0], ROW, FAMILY, QUALIFIERS[0], 1);
317     assertIncrementKey(kvs[1], ROW, FAMILY, QUALIFIERS[1], 1);
318     assertIncrementKey(kvs[2], ROW, FAMILY, QUALIFIERS[2], 1);
319 
320     // Now try multiple columns again
321     inc = new Increment(ROW);
322     for (int i = 0; i < QUALIFIERS.length; i++) {
323       inc.addColumn(FAMILY, QUALIFIERS[i], 1);
324       inc.addColumn(FAMILY, QUALIFIERS[i], 1);
325     }
326     ht.increment(inc);
327 
328     // Verify
329     r = ht.get(get);
330     kvs = r.rawCells();
331     assertEquals(3, kvs.length);
332     assertIncrementKey(kvs[0], ROW, FAMILY, QUALIFIERS[0], 2);
333     assertIncrementKey(kvs[1], ROW, FAMILY, QUALIFIERS[1], 2);
334     assertIncrementKey(kvs[2], ROW, FAMILY, QUALIFIERS[2], 2);
335     
336     ht.close();
337   }
338 
339   @Test
340   public void testIncrement() throws Exception {
341     LOG.info("Starting " + this.name.getMethodName());
342     final TableName TABLENAME =
343         TableName.valueOf(filterStringSoTableNameSafe(this.name.getMethodName()));
344     Table ht = TEST_UTIL.createTable(TABLENAME, FAMILY);
345 
346     byte [][] ROWS = new byte [][] {
347         Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c"),
348         Bytes.toBytes("d"), Bytes.toBytes("e"), Bytes.toBytes("f"),
349         Bytes.toBytes("g"), Bytes.toBytes("h"), Bytes.toBytes("i")
350     };
351     byte [][] QUALIFIERS = new byte [][] {
352         Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c"),
353         Bytes.toBytes("d"), Bytes.toBytes("e"), Bytes.toBytes("f"),
354         Bytes.toBytes("g"), Bytes.toBytes("h"), Bytes.toBytes("i")
355     };
356 
357     // Do some simple single-column increments
358 
359     // First with old API
360     ht.incrementColumnValue(ROW, FAMILY, QUALIFIERS[0], 1);
361     ht.incrementColumnValue(ROW, FAMILY, QUALIFIERS[1], 2);
362     ht.incrementColumnValue(ROW, FAMILY, QUALIFIERS[2], 3);
363     ht.incrementColumnValue(ROW, FAMILY, QUALIFIERS[3], 4);
364 
365     // Now increment things incremented with old and do some new
366     Increment inc = new Increment(ROW);
367     inc.addColumn(FAMILY, QUALIFIERS[1], 1);
368     inc.addColumn(FAMILY, QUALIFIERS[3], 1);
369     inc.addColumn(FAMILY, QUALIFIERS[4], 1);
370     ht.increment(inc);
371 
372     // Verify expected results
373     Get get = new Get(ROW);
374     if (this.fast) get.setIsolationLevel(IsolationLevel.READ_UNCOMMITTED);
375     Result r = ht.get(get);
376     Cell [] kvs = r.rawCells();
377     assertEquals(5, kvs.length);
378     assertIncrementKey(kvs[0], ROW, FAMILY, QUALIFIERS[0], 1);
379     assertIncrementKey(kvs[1], ROW, FAMILY, QUALIFIERS[1], 3);
380     assertIncrementKey(kvs[2], ROW, FAMILY, QUALIFIERS[2], 3);
381     assertIncrementKey(kvs[3], ROW, FAMILY, QUALIFIERS[3], 5);
382     assertIncrementKey(kvs[4], ROW, FAMILY, QUALIFIERS[4], 1);
383 
384     // Now try multiple columns by different amounts
385     inc = new Increment(ROWS[0]);
386     for (int i=0;i<QUALIFIERS.length;i++) {
387       inc.addColumn(FAMILY, QUALIFIERS[i], i+1);
388     }
389     ht.increment(inc);
390     // Verify
391     get = new Get(ROWS[0]);
392     if (this.fast) get.setIsolationLevel(IsolationLevel.READ_UNCOMMITTED);
393     r = ht.get(get);
394     kvs = r.rawCells();
395     assertEquals(QUALIFIERS.length, kvs.length);
396     for (int i=0;i<QUALIFIERS.length;i++) {
397       assertIncrementKey(kvs[i], ROWS[0], FAMILY, QUALIFIERS[i], i+1);
398     }
399 
400     // Re-increment them
401     inc = new Increment(ROWS[0]);
402     for (int i=0;i<QUALIFIERS.length;i++) {
403       inc.addColumn(FAMILY, QUALIFIERS[i], i+1);
404     }
405     ht.increment(inc);
406     // Verify
407     r = ht.get(get);
408     kvs = r.rawCells();
409     assertEquals(QUALIFIERS.length, kvs.length);
410     for (int i=0;i<QUALIFIERS.length;i++) {
411       assertIncrementKey(kvs[i], ROWS[0], FAMILY, QUALIFIERS[i], 2*(i+1));
412     }
413 
414     // Verify that an Increment of an amount of zero, returns current count; i.e. same as for above
415     // test, that is: 2 * (i + 1).
416     inc = new Increment(ROWS[0]);
417     for (int i = 0; i < QUALIFIERS.length; i++) {
418       inc.addColumn(FAMILY, QUALIFIERS[i], 0);
419     }
420     ht.increment(inc);
421     r = ht.get(get);
422     kvs = r.rawCells();
423     assertEquals(QUALIFIERS.length, kvs.length);
424     for (int i = 0; i < QUALIFIERS.length; i++) {
425       assertIncrementKey(kvs[i], ROWS[0], FAMILY, QUALIFIERS[i], 2*(i+1));
426     }
427   }
428 
429 
430   /**
431    * Call over to the adjacent class's method of same name.
432    */
433   static void assertIncrementKey(Cell key, byte [] row, byte [] family,
434       byte [] qualifier, long value) throws Exception {
435     TestFromClientSide.assertIncrementKey(key, row, family, qualifier, value);
436   }
437 
438   public static String filterStringSoTableNameSafe(final String str) {
439     return str.replaceAll("\\[fast\\=(.*)\\]", ".FAST.is.$1");
440   }
441 }