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.omid.transaction;
19  
20  import com.google.common.base.Optional;
21  import com.google.common.collect.Maps;
22  import com.google.common.util.concurrent.SettableFuture;
23  import org.apache.omid.committable.CommitTable;
24  import org.apache.omid.committable.CommitTable.CommitTimestamp;
25  import org.apache.omid.metrics.NullMetricsProvider;
26  import org.apache.omid.transaction.HBaseTransactionManager.CommitTimestampLocatorImpl;
27  import org.apache.hadoop.hbase.client.HTable;
28  import org.apache.hadoop.hbase.client.Put;
29  import org.apache.hadoop.hbase.util.Bytes;
30  import org.testng.ITestContext;
31  import org.testng.annotations.Test;
32  
33  import java.util.Map;
34  
35  import static org.apache.omid.committable.CommitTable.CommitTimestamp.Location.CACHE;
36  import static org.apache.omid.committable.CommitTable.CommitTimestamp.Location.COMMIT_TABLE;
37  import static org.apache.omid.committable.CommitTable.CommitTimestamp.Location.NOT_PRESENT;
38  import static org.apache.omid.committable.CommitTable.CommitTimestamp.Location.SHADOW_CELL;
39  import static org.mockito.Matchers.any;
40  import static org.mockito.Mockito.doReturn;
41  import static org.mockito.Mockito.doThrow;
42  import static org.mockito.Mockito.spy;
43  import static org.testng.Assert.assertEquals;
44  import static org.testng.Assert.assertFalse;
45  import static org.testng.Assert.assertTrue;
46  
47  @Test(groups = "sharedHBase")
48  public class TestHBaseTransactionClient extends OmidTestBase {
49  
50      private static final byte[] row1 = Bytes.toBytes("test-is-committed1");
51      private static final byte[] row2 = Bytes.toBytes("test-is-committed2");
52      private static final byte[] family = Bytes.toBytes(TEST_FAMILY);
53      private static final byte[] qualifier = Bytes.toBytes("testdata");
54      private static final byte[] data1 = Bytes.toBytes("testWrite-1");
55  
56      @Test(timeOut = 30_000)
57      public void testIsCommitted(ITestContext context) throws Exception {
58          TransactionManager tm = newTransactionManager(context);
59          TTable table = new TTable(hbaseConf, TEST_TABLE);
60  
61          HBaseTransaction t1 = (HBaseTransaction) tm.begin();
62  
63          Put put = new Put(row1);
64          put.add(family, qualifier, data1);
65          table.put(t1, put);
66          tm.commit(t1);
67  
68          HBaseTransaction t2 = (HBaseTransaction) tm.begin();
69          put = new Put(row2);
70          put.add(family, qualifier, data1);
71          table.put(t2, put);
72          table.getHTable().flushCommits();
73  
74          HBaseTransaction t3 = (HBaseTransaction) tm.begin();
75          put = new Put(row2);
76          put.add(family, qualifier, data1);
77          table.put(t3, put);
78          tm.commit(t3);
79  
80          HTable htable = new HTable(hbaseConf, TEST_TABLE);
81          HBaseCellId hBaseCellId1 = new HBaseCellId(htable, row1, family, qualifier, t1.getStartTimestamp());
82          HBaseCellId hBaseCellId2 = new HBaseCellId(htable, row2, family, qualifier, t2.getStartTimestamp());
83          HBaseCellId hBaseCellId3 = new HBaseCellId(htable, row2, family, qualifier, t3.getStartTimestamp());
84  
85          HBaseTransactionClient hbaseTm = (HBaseTransactionClient) newTransactionManager(context);
86          assertTrue(hbaseTm.isCommitted(hBaseCellId1), "row1 should be committed");
87          assertFalse(hbaseTm.isCommitted(hBaseCellId2), "row2 should not be committed for kv2");
88          assertTrue(hbaseTm.isCommitted(hBaseCellId3), "row2 should be committed for kv3");
89      }
90  
91      @Test(timeOut = 30_000)
92      public void testCrashAfterCommit(ITestContext context) throws Exception {
93          PostCommitActions syncPostCommitter =
94                  spy(new HBaseSyncPostCommitter(new NullMetricsProvider(), getCommitTable(context).getClient()));
95          AbstractTransactionManager tm = (AbstractTransactionManager) newTransactionManager(context, syncPostCommitter);
96          // The following line emulates a crash after commit that is observed in (*) below
97          doThrow(new RuntimeException()).when(syncPostCommitter).updateShadowCells(any(HBaseTransaction.class));
98  
99          TTable table = new TTable(hbaseConf, TEST_TABLE);
100 
101         HBaseTransaction t1 = (HBaseTransaction) tm.begin();
102 
103         // Test shadow cell are created properly
104         Put put = new Put(row1);
105         put.add(family, qualifier, data1);
106         table.put(t1, put);
107         try {
108             tm.commit(t1);
109         } catch (Exception e) { // (*) crash
110             // Do nothing
111         }
112 
113         assertTrue(CellUtils.hasCell(row1, family, qualifier, t1.getStartTimestamp(), new TTableCellGetterAdapter(table)),
114                    "Cell should be there");
115         assertFalse(CellUtils.hasShadowCell(row1, family, qualifier, t1.getStartTimestamp(), new TTableCellGetterAdapter(table)),
116                     "Shadow cell should not be there");
117 
118         HTable htable = new HTable(hbaseConf, TEST_TABLE);
119         HBaseCellId hBaseCellId = new HBaseCellId(htable, row1, family, qualifier, t1.getStartTimestamp());
120 
121         HBaseTransactionClient hbaseTm = (HBaseTransactionClient) newTransactionManager(context);
122         assertTrue(hbaseTm.isCommitted(hBaseCellId), "row1 should be committed");
123     }
124 
125     @Test(timeOut = 30_000)
126     public void testReadCommitTimestampFromCommitTable(ITestContext context) throws Exception {
127 
128         final long NON_EXISTING_CELL_TS = 1000L;
129 
130         PostCommitActions syncPostCommitter =
131                 spy(new HBaseSyncPostCommitter(new NullMetricsProvider(), getCommitTable(context).getClient()));
132         AbstractTransactionManager tm = (AbstractTransactionManager) newTransactionManager(context, syncPostCommitter);
133         // The following line emulates a crash after commit that is observed in (*) below
134         doThrow(new RuntimeException()).when(syncPostCommitter).updateShadowCells(any(HBaseTransaction.class));
135 
136         // Test that a non-existing cell timestamp returns an empty result
137         Optional<CommitTimestamp> optionalCT = tm.commitTableClient.getCommitTimestamp(NON_EXISTING_CELL_TS).get();
138         assertFalse(optionalCT.isPresent());
139 
140         try (TTable table = new TTable(hbaseConf, TEST_TABLE)) {
141             // Test that we get an invalidation mark for an invalidated transaction
142 
143             // Start a transaction and invalidate it before commiting it
144             HBaseTransaction tx1 = (HBaseTransaction) tm.begin();
145             Put put = new Put(row1);
146             put.add(family, qualifier, data1);
147             table.put(tx1, put);
148 
149             assertTrue(tm.commitTableClient.tryInvalidateTransaction(tx1.getStartTimestamp()).get());
150             optionalCT = tm.commitTableClient.getCommitTimestamp(tx1.getStartTimestamp()).get();
151             assertTrue(optionalCT.isPresent());
152             CommitTimestamp ct = optionalCT.get();
153             assertFalse(ct.isValid());
154             assertEquals(ct.getValue(), CommitTable.INVALID_TRANSACTION_MARKER);
155             assertTrue(ct.getLocation().compareTo(COMMIT_TABLE) == 0);
156 
157             // Finally test that we get the right commit timestamp for a committed tx
158             // that couldn't get
159             HBaseTransaction tx2 = (HBaseTransaction) tm.begin();
160             Put otherPut = new Put(row1);
161             otherPut.add(family, qualifier, data1);
162             table.put(tx2, otherPut);
163             try {
164                 tm.commit(tx2);
165             } catch (Exception e) { // (*) crash
166                 // Do nothing
167             }
168 
169             optionalCT = tm.commitTableClient.getCommitTimestamp(tx2.getStartTimestamp()).get();
170             assertTrue(optionalCT.isPresent());
171             ct = optionalCT.get();
172             assertTrue(ct.isValid());
173             assertEquals(ct.getValue(), tx2.getCommitTimestamp());
174             assertTrue(ct.getLocation().compareTo(COMMIT_TABLE) == 0);
175         }
176     }
177 
178     @Test(timeOut = 30_000)
179     public void testReadCommitTimestampFromShadowCell(ITestContext context) throws Exception {
180 
181         final long NON_EXISTING_CELL_TS = 1L;
182 
183         HBaseTransactionManager tm = (HBaseTransactionManager) newTransactionManager(context);
184 
185         try (TTable table = new TTable(hbaseConf, TEST_TABLE)) {
186 
187             // Test first we can not found a non-existent cell ts
188             HBaseCellId hBaseCellId = new HBaseCellId(table.getHTable(), row1, family, qualifier, NON_EXISTING_CELL_TS);
189             // Set an empty cache to allow to bypass the checking
190             CommitTimestampLocator ctLocator = new CommitTimestampLocatorImpl(hBaseCellId,
191                     Maps.<Long, Long>newHashMap());
192             Optional<CommitTimestamp> optionalCT = tm
193                     .readCommitTimestampFromShadowCell(NON_EXISTING_CELL_TS, ctLocator);
194             assertFalse(optionalCT.isPresent());
195 
196             // Then test that for a transaction committed, we get the right CT
197             HBaseTransaction tx1 = (HBaseTransaction) tm.begin();
198             Put put = new Put(row1);
199             put.add(family, qualifier, data1);
200             table.put(tx1, put);
201             tm.commit(tx1);
202             // Upon commit, the commit data should be in the shadow cells, so test it
203             optionalCT = tm.readCommitTimestampFromShadowCell(tx1.getStartTimestamp(), ctLocator);
204             assertTrue(optionalCT.isPresent());
205             CommitTimestamp ct = optionalCT.get();
206             assertTrue(ct.isValid());
207             assertEquals(ct.getValue(), tx1.getCommitTimestamp());
208             assertTrue(ct.getLocation().compareTo(SHADOW_CELL) == 0);
209 
210         }
211 
212     }
213 
214     // Tests step 1 in AbstractTransactionManager.locateCellCommitTimestamp()
215     @Test(timeOut = 30_000)
216     public void testCellCommitTimestampIsLocatedInCache(ITestContext context) throws Exception {
217 
218         final long CELL_ST = 1L;
219         final long CELL_CT = 2L;
220 
221         HBaseTransactionManager tm = (HBaseTransactionManager) newTransactionManager(context);
222 
223         // Pre-load the element to look for in the cache
224         HTable table = new HTable(hbaseConf, TEST_TABLE);
225         HBaseCellId hBaseCellId = new HBaseCellId(table, row1, family, qualifier, CELL_ST);
226         Map<Long, Long> fakeCache = Maps.newHashMap();
227         fakeCache.put(CELL_ST, CELL_CT);
228 
229         // Then test that locator finds it in the cache
230         CommitTimestampLocator ctLocator = new CommitTimestampLocatorImpl(hBaseCellId, fakeCache);
231         CommitTimestamp ct = tm.locateCellCommitTimestamp(CELL_ST, tm.tsoClient.getEpoch(), ctLocator);
232         assertTrue(ct.isValid());
233         assertEquals(ct.getValue(), CELL_CT);
234         assertTrue(ct.getLocation().compareTo(CACHE) == 0);
235 
236     }
237 
238     // Tests step 2 in AbstractTransactionManager.locateCellCommitTimestamp()
239     // Note: This test is very similar to testCrashAfterCommit() above so
240     // maybe we should merge them in this test, adding the missing assertions
241     @Test(timeOut = 30_000)
242     public void testCellCommitTimestampIsLocatedInCommitTable(ITestContext context) throws Exception {
243 
244         PostCommitActions syncPostCommitter =
245                 spy(new HBaseSyncPostCommitter(new NullMetricsProvider(), getCommitTable(context).getClient()));
246         AbstractTransactionManager tm = (AbstractTransactionManager) newTransactionManager(context, syncPostCommitter);
247         // The following line emulates a crash after commit that is observed in (*) below
248         doThrow(new RuntimeException()).when(syncPostCommitter).updateShadowCells(any(HBaseTransaction.class));
249 
250         try (TTable table = new TTable(hbaseConf, TEST_TABLE)) {
251             // Commit a transaction that is broken on commit to avoid
252             // write to the shadow cells and avoid cleaning the commit table
253             HBaseTransaction tx1 = (HBaseTransaction) tm.begin();
254             Put put = new Put(row1);
255             put.add(family, qualifier, data1);
256             table.put(tx1, put);
257             try {
258                 tm.commit(tx1);
259             } catch (Exception e) { // (*) crash
260                 // Do nothing
261             }
262 
263             // Test the locator finds the appropriate data in the commit table
264             HBaseCellId hBaseCellId = new HBaseCellId(table.getHTable(), row1, family, qualifier,
265                     tx1.getStartTimestamp());
266             CommitTimestampLocator ctLocator = new CommitTimestampLocatorImpl(hBaseCellId,
267                     Maps.<Long, Long>newHashMap());
268             CommitTimestamp ct = tm.locateCellCommitTimestamp(tx1.getStartTimestamp(), tm.tsoClient.getEpoch(),
269                     ctLocator);
270             assertTrue(ct.isValid());
271             long expectedCommitTS = tx1.getStartTimestamp() + 1;
272             assertEquals(ct.getValue(), expectedCommitTS);
273             assertTrue(ct.getLocation().compareTo(COMMIT_TABLE) == 0);
274         }
275 
276     }
277 
278     // Tests step 3 in AbstractTransactionManager.locateCellCommitTimestamp()
279     @Test(timeOut = 30_000)
280     public void testCellCommitTimestampIsLocatedInShadowCells(ITestContext context) throws Exception {
281 
282         HBaseTransactionManager tm = (HBaseTransactionManager) newTransactionManager(context);
283 
284         try (TTable table = new TTable(hbaseConf, TEST_TABLE)) {
285             // Commit a transaction to add ST/CT in commit table
286             HBaseTransaction tx1 = (HBaseTransaction) tm.begin();
287             Put put = new Put(row1);
288             put.add(family, qualifier, data1);
289             table.put(tx1, put);
290             tm.commit(tx1);
291             // Upon commit, the commit data should be in the shadow cells
292 
293             // Test the locator finds the appropriate data in the shadow cells
294             HBaseCellId hBaseCellId = new HBaseCellId(table.getHTable(), row1, family, qualifier,
295                     tx1.getStartTimestamp());
296             CommitTimestampLocator ctLocator = new CommitTimestampLocatorImpl(hBaseCellId,
297                     Maps.<Long, Long>newHashMap());
298             CommitTimestamp ct = tm.locateCellCommitTimestamp(tx1.getStartTimestamp(), tm.tsoClient.getEpoch(),
299                     ctLocator);
300             assertTrue(ct.isValid());
301             assertEquals(ct.getValue(), tx1.getCommitTimestamp());
302             assertTrue(ct.getLocation().compareTo(SHADOW_CELL) == 0);
303         }
304 
305     }
306 
307     // Tests step 4 in AbstractTransactionManager.locateCellCommitTimestamp()
308     @Test(timeOut = 30_000)
309     public void testCellFromTransactionInPreviousEpochGetsInvalidComitTimestamp(ITestContext context) throws Exception {
310 
311         final long CURRENT_EPOCH_FAKE = 1000L;
312 
313         CommitTable.Client commitTableClient = spy(getCommitTable(context).getClient());
314         AbstractTransactionManager tm = spy((AbstractTransactionManager) newTransactionManager(context, commitTableClient));
315         // The following lines allow to reach step 4)
316         // in AbstractTransactionManager.locateCellCommitTimestamp()
317         SettableFuture<Optional<CommitTimestamp>> f = SettableFuture.create();
318         f.set(Optional.<CommitTimestamp>absent());
319         doReturn(f).when(commitTableClient).getCommitTimestamp(any(Long.class));
320         doReturn(Optional.<CommitTimestamp>absent()).when(tm).readCommitTimestampFromShadowCell(any(Long.class),
321                 any(CommitTimestampLocator.class));
322 
323         try (TTable table = new TTable(hbaseConf, TEST_TABLE)) {
324 
325             // Commit a transaction to add ST/CT in commit table
326             HBaseTransaction tx1 = (HBaseTransaction) tm.begin();
327             Put put = new Put(row1);
328             put.add(family, qualifier, data1);
329             table.put(tx1, put);
330             tm.commit(tx1);
331             // Upon commit, the commit data should be in the shadow cells
332 
333             // Test a transaction in the previous epoch gets an InvalidCommitTimestamp class
334             HBaseCellId hBaseCellId = new HBaseCellId(table.getHTable(), row1, family, qualifier,
335                     tx1.getStartTimestamp());
336             CommitTimestampLocator ctLocator = new CommitTimestampLocatorImpl(hBaseCellId,
337                     Maps.<Long, Long>newHashMap());
338             // Fake the current epoch to simulate a newer TSO
339             CommitTimestamp ct = tm.locateCellCommitTimestamp(tx1.getStartTimestamp(), CURRENT_EPOCH_FAKE, ctLocator);
340             assertFalse(ct.isValid());
341             assertEquals(ct.getValue(), CommitTable.INVALID_TRANSACTION_MARKER);
342             assertTrue(ct.getLocation().compareTo(COMMIT_TABLE) == 0);
343         }
344     }
345 
346     // Tests step 5 in AbstractTransactionManager.locateCellCommitTimestamp()
347     @Test(timeOut = 30_000)
348     public void testCellCommitTimestampIsLocatedInCommitTableAfterNotBeingInvalidated(ITestContext context) throws Exception {
349 
350         CommitTable.Client commitTableClient = spy(getCommitTable(context).getClient());
351         PostCommitActions syncPostCommitter =
352                 spy(new HBaseSyncPostCommitter(new NullMetricsProvider(), commitTableClient));
353         AbstractTransactionManager tm = spy((AbstractTransactionManager) newTransactionManager(context, syncPostCommitter));
354 
355         // The following line emulates a crash after commit that is observed in (*) below
356         doThrow(new RuntimeException()).when(syncPostCommitter).updateShadowCells(any(HBaseTransaction.class));
357         // The next two lines avoid steps 2) and 3) and go directly to step 5)
358         // in AbstractTransactionManager.locateCellCommitTimestamp()
359         SettableFuture<Optional<CommitTimestamp>> f = SettableFuture.create();
360         f.set(Optional.<CommitTimestamp>absent());
361         doReturn(f).doCallRealMethod().when(commitTableClient).getCommitTimestamp(any(Long.class));
362         doReturn(Optional.<CommitTimestamp>absent()).when(tm).readCommitTimestampFromShadowCell(any(Long.class),
363                 any(CommitTimestampLocator.class));
364 
365         try (TTable table = new TTable(hbaseConf, TEST_TABLE)) {
366 
367             // Commit a transaction that is broken on commit to avoid
368             // write to the shadow cells and avoid cleaning the commit table
369             HBaseTransaction tx1 = (HBaseTransaction) tm.begin();
370             Put put = new Put(row1);
371             put.add(family, qualifier, data1);
372             table.put(tx1, put);
373             try {
374                 tm.commit(tx1);
375             } catch (Exception e) { // (*) crash
376                 // Do nothing
377             }
378 
379             // Test the locator finds the appropriate data in the commit table
380             HBaseCellId hBaseCellId = new HBaseCellId(table.getHTable(), row1, family, qualifier,
381                     tx1.getStartTimestamp());
382             CommitTimestampLocator ctLocator = new CommitTimestampLocatorImpl(hBaseCellId,
383                     Maps.<Long, Long>newHashMap());
384             CommitTimestamp ct = tm.locateCellCommitTimestamp(tx1.getStartTimestamp(), tm.tsoClient.getEpoch(),
385                     ctLocator);
386             assertTrue(ct.isValid());
387             assertEquals(ct.getValue(), tx1.getCommitTimestamp());
388             assertTrue(ct.getLocation().compareTo(COMMIT_TABLE) == 0);
389         }
390 
391     }
392 
393     // Tests step 6 in AbstractTransactionManager.locateCellCommitTimestamp()
394     @Test(timeOut = 30_000)
395     public void testCellCommitTimestampIsLocatedInShadowCellsAfterNotBeingInvalidated(ITestContext context) throws Exception {
396 
397         CommitTable.Client commitTableClient = spy(getCommitTable(context).getClient());
398         AbstractTransactionManager tm = spy((AbstractTransactionManager) newTransactionManager(context, commitTableClient));
399         // The next two lines avoid steps 2), 3) and 5) and go directly to step 6)
400         // in AbstractTransactionManager.locateCellCommitTimestamp()
401         SettableFuture<Optional<CommitTimestamp>> f = SettableFuture.create();
402         f.set(Optional.<CommitTimestamp>absent());
403         doReturn(f).when(commitTableClient).getCommitTimestamp(any(Long.class));
404         doReturn(Optional.<CommitTimestamp>absent()).doCallRealMethod()
405                 .when(tm).readCommitTimestampFromShadowCell(any(Long.class), any(CommitTimestampLocator.class));
406 
407         try (TTable table = new TTable(hbaseConf, TEST_TABLE)) {
408 
409             // Commit a transaction to add ST/CT in commit table
410             HBaseTransaction tx1 = (HBaseTransaction) tm.begin();
411             Put put = new Put(row1);
412             put.add(family, qualifier, data1);
413             table.put(tx1, put);
414             tm.commit(tx1);
415             // Upon commit, the commit data should be in the shadow cells
416 
417             // Test the locator finds the appropriate data in the shadow cells
418             HBaseCellId hBaseCellId = new HBaseCellId(table.getHTable(), row1, family, qualifier,
419                     tx1.getStartTimestamp());
420             CommitTimestampLocator ctLocator = new CommitTimestampLocatorImpl(hBaseCellId,
421                     Maps.<Long, Long>newHashMap());
422             CommitTimestamp ct = tm.locateCellCommitTimestamp(tx1.getStartTimestamp(), tm.tsoClient.getEpoch(),
423                     ctLocator);
424             assertTrue(ct.isValid());
425             assertEquals(ct.getValue(), tx1.getCommitTimestamp());
426             assertTrue(ct.getLocation().compareTo(SHADOW_CELL) == 0);
427         }
428 
429     }
430 
431     // Tests last step in AbstractTransactionManager.locateCellCommitTimestamp()
432     @Test(timeOut = 30_000)
433     public void testCTLocatorReturnsAValidCTWhenNotPresent(ITestContext context) throws Exception {
434 
435         final long CELL_TS = 1L;
436 
437         CommitTable.Client commitTableClient = spy(getCommitTable(context).getClient());
438         AbstractTransactionManager tm = spy((AbstractTransactionManager) newTransactionManager(context, commitTableClient));
439         // The following lines allow to reach the last return statement
440         SettableFuture<Optional<CommitTimestamp>> f = SettableFuture.create();
441         f.set(Optional.<CommitTimestamp>absent());
442         doReturn(f).when(commitTableClient).getCommitTimestamp(any(Long.class));
443         doReturn(Optional.<CommitTimestamp>absent()).when(tm).readCommitTimestampFromShadowCell(any(Long.class),
444                 any(CommitTimestampLocator.class));
445 
446         try (TTable table = new TTable(hbaseConf, TEST_TABLE)) {
447             HBaseCellId hBaseCellId = new HBaseCellId(table.getHTable(), row1, family, qualifier, CELL_TS);
448             CommitTimestampLocator ctLocator = new CommitTimestampLocatorImpl(hBaseCellId,
449                     Maps.<Long, Long>newHashMap());
450             CommitTimestamp ct = tm.locateCellCommitTimestamp(CELL_TS, tm.tsoClient.getEpoch(), ctLocator);
451             assertTrue(ct.isValid());
452             assertEquals(ct.getValue(), -1L);
453             assertTrue(ct.getLocation().compareTo(NOT_PRESENT) == 0);
454         }
455 
456     }
457 
458 }