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.replication.regionserver;
20  
21  import static org.junit.Assert.assertEquals;
22  
23  import java.security.SecureRandom;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Random;
32  import java.util.Set;
33  import java.util.concurrent.atomic.AtomicBoolean;
34  
35  import org.apache.hadoop.hbase.TableNotFoundException;
36  import org.apache.hadoop.hbase.client.Admin;
37  import org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException;
38  import org.apache.hadoop.hbase.client.Table;
39  import org.apache.hadoop.hbase.testclassification.LargeTests;
40  import org.apache.hadoop.hbase.util.ByteStringer;
41  import org.apache.commons.logging.Log;
42  import org.apache.commons.logging.LogFactory;
43  import org.apache.hadoop.conf.Configuration;
44  import org.apache.hadoop.fs.FileSystem;
45  import org.apache.hadoop.fs.FileUtil;
46  import org.apache.hadoop.fs.Path;
47  import org.apache.hadoop.hbase.Cell;
48  import org.apache.hadoop.hbase.CellUtil;
49  import org.apache.hadoop.hbase.HBaseTestingUtility;
50  import org.apache.hadoop.hbase.HConstants;
51  import org.apache.hadoop.hbase.HRegionInfo;
52  import org.apache.hadoop.hbase.KeyValue;
53  import org.apache.hadoop.hbase.Stoppable;
54  import org.apache.hadoop.hbase.TableName;
55  import org.apache.hadoop.hbase.client.Connection;
56  import org.apache.hadoop.hbase.client.ConnectionFactory;
57  import org.apache.hadoop.hbase.client.Get;
58  import org.apache.hadoop.hbase.client.RegionLocator;
59  import org.apache.hadoop.hbase.client.Result;
60  import org.apache.hadoop.hbase.client.ResultScanner;
61  import org.apache.hadoop.hbase.client.Scan;
62  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
63  import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.WALEntry;
64  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.UUID;
65  import org.apache.hadoop.hbase.protobuf.generated.WALProtos;
66  import org.apache.hadoop.hbase.protobuf.generated.WALProtos.WALKey;
67  import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
68  import org.apache.hadoop.hbase.util.Bytes;
69  import org.apache.hadoop.hbase.util.FSUtils;
70  import org.apache.hadoop.hbase.util.HFileTestUtil;
71  import org.junit.AfterClass;
72  import org.junit.Assert;
73  import org.junit.Before;
74  import org.junit.BeforeClass;
75  import org.junit.Test;
76  import org.junit.experimental.categories.Category;
77  
78  @Category(LargeTests.class)
79  public class TestReplicationSink {
80    private static final Log LOG = LogFactory.getLog(TestReplicationSink.class);
81    private static final int BATCH_SIZE = 10;
82  
83    protected final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
84  
85    protected static ReplicationSink SINK;
86  
87    protected static final TableName TABLE_NAME1 = TableName.valueOf("table1");
88    protected static final TableName TABLE_NAME2 = TableName.valueOf("table2");
89  
90    protected static final byte[] FAM_NAME1 = Bytes.toBytes("info1");
91    protected static final byte[] FAM_NAME2 = Bytes.toBytes("info2");
92  
93    protected static Table table1;
94    protected static Stoppable STOPPABLE = new Stoppable() {
95      final AtomicBoolean stop = new AtomicBoolean(false);
96  
97      @Override
98      public boolean isStopped() {
99        return this.stop.get();
100     }
101 
102     @Override
103     public void stop(String why) {
104       LOG.info("STOPPING BECAUSE: " + why);
105       this.stop.set(true);
106     }
107 
108   };
109 
110   protected static Table table2;
111   protected static String baseNamespaceDir;
112   protected static String hfileArchiveDir;
113   protected static String replicationClusterId;
114 
115    /**
116    * @throws java.lang.Exception
117    */
118   @BeforeClass
119   public static void setUpBeforeClass() throws Exception {
120     TEST_UTIL.getConfiguration().setBoolean("dfs.support.append", true);
121     TEST_UTIL.getConfiguration().setBoolean(HConstants.REPLICATION_ENABLE_KEY,
122         HConstants.REPLICATION_ENABLE_DEFAULT);
123     TEST_UTIL.getConfiguration().set("hbase.replication.source.fs.conf.provider",
124       TestSourceFSConfigurationProvider.class.getCanonicalName());
125     TEST_UTIL.startMiniCluster(3);
126     SINK = new ReplicationSink(new Configuration(TEST_UTIL.getConfiguration()));
127     table1 = TEST_UTIL.createTable(TABLE_NAME1, FAM_NAME1);
128     table2 = TEST_UTIL.createTable(TABLE_NAME2, FAM_NAME2);
129     Path rootDir = FSUtils.getRootDir(TEST_UTIL.getConfiguration());
130     baseNamespaceDir = new Path(rootDir, new Path(HConstants.BASE_NAMESPACE_DIR)).toString();
131     hfileArchiveDir = new Path(rootDir, new Path(HConstants.HFILE_ARCHIVE_DIRECTORY)).toString();
132     replicationClusterId = "12345";
133   }
134 
135   /**
136    * @throws java.lang.Exception
137    */
138   @AfterClass
139   public static void tearDownAfterClass() throws Exception {
140     STOPPABLE.stop("Shutting down");
141     TEST_UTIL.shutdownMiniCluster();
142   }
143 
144   /**
145    * @throws java.lang.Exception
146    */
147   @Before
148   public void setUp() throws Exception {
149     table1 = TEST_UTIL.deleteTableData(TABLE_NAME1);
150     table2 = TEST_UTIL.deleteTableData(TABLE_NAME2);
151   }
152 
153   /**
154    * Insert a whole batch of entries
155    * @throws Exception
156    */
157   @Test
158   public void testBatchSink() throws Exception {
159     List<WALEntry> entries = new ArrayList<WALEntry>(BATCH_SIZE);
160     List<Cell> cells = new ArrayList<Cell>();
161     for(int i = 0; i < BATCH_SIZE; i++) {
162       entries.add(createEntry(TABLE_NAME1, i, KeyValue.Type.Put, cells));
163     }
164     SINK.replicateEntries(entries, CellUtil.createCellScanner(cells.iterator()),
165       replicationClusterId, baseNamespaceDir, hfileArchiveDir);
166     Scan scan = new Scan();
167     ResultScanner scanRes = table1.getScanner(scan);
168     assertEquals(BATCH_SIZE, scanRes.next(BATCH_SIZE).length);
169   }
170 
171   /**
172    * Insert a mix of puts and deletes
173    * @throws Exception
174    */
175   @Test
176   public void testMixedPutDelete() throws Exception {
177     List<WALEntry> entries = new ArrayList<WALEntry>(BATCH_SIZE/2);
178     List<Cell> cells = new ArrayList<Cell>();
179     for(int i = 0; i < BATCH_SIZE/2; i++) {
180       entries.add(createEntry(TABLE_NAME1, i, KeyValue.Type.Put, cells));
181     }
182     SINK.replicateEntries(entries, CellUtil.createCellScanner(cells), replicationClusterId,
183       baseNamespaceDir, hfileArchiveDir);
184 
185     entries = new ArrayList<WALEntry>(BATCH_SIZE);
186     cells = new ArrayList<Cell>();
187     for(int i = 0; i < BATCH_SIZE; i++) {
188       entries.add(createEntry(TABLE_NAME1, i,
189           i % 2 != 0 ? KeyValue.Type.Put: KeyValue.Type.DeleteColumn, cells));
190     }
191 
192     SINK.replicateEntries(entries, CellUtil.createCellScanner(cells.iterator()),
193       replicationClusterId, baseNamespaceDir, hfileArchiveDir);
194     Scan scan = new Scan();
195     ResultScanner scanRes = table1.getScanner(scan);
196     assertEquals(BATCH_SIZE/2, scanRes.next(BATCH_SIZE).length);
197   }
198 
199   @Test
200   public void testLargeEditsPutDelete() throws Exception {
201     List<WALEntry> entries = new ArrayList<>();
202     List<Cell> cells = new ArrayList<>();
203     for (int i = 0; i < 5510; i++) {
204       entries.add(createEntry(TABLE_NAME1, i, KeyValue.Type.Put, cells));
205     }
206     SINK.replicateEntries(entries, CellUtil.createCellScanner(cells), replicationClusterId,
207       baseNamespaceDir, hfileArchiveDir);
208 
209     ResultScanner resultScanner = table1.getScanner(new Scan());
210     int totalRows = 0;
211     while (resultScanner.next() != null) {
212       totalRows++;
213     }
214     assertEquals(5510, totalRows);
215 
216     entries = new ArrayList<>();
217     cells = new ArrayList<>();
218     for (int i = 0; i < 11000; i++) {
219       entries.add(
220         createEntry(TABLE_NAME1, i, i % 2 != 0 ? KeyValue.Type.Put : KeyValue.Type.DeleteColumn,
221           cells));
222     }
223     SINK.replicateEntries(entries, CellUtil.createCellScanner(cells), replicationClusterId,
224       baseNamespaceDir, hfileArchiveDir);
225     resultScanner = table1.getScanner(new Scan());
226     totalRows = 0;
227     while (resultScanner.next() != null) {
228       totalRows++;
229     }
230     assertEquals(5500, totalRows);
231   }
232 
233   /**
234    * Insert to 2 different tables
235    * @throws Exception
236    */
237   @Test
238   public void testMixedPutTables() throws Exception {
239     List<WALEntry> entries = new ArrayList<WALEntry>(BATCH_SIZE/2);
240     List<Cell> cells = new ArrayList<Cell>();
241     for(int i = 0; i < BATCH_SIZE; i++) {
242       entries.add(createEntry( i % 2 == 0 ? TABLE_NAME2 : TABLE_NAME1,
243               i, KeyValue.Type.Put, cells));
244     }
245 
246     SINK.replicateEntries(entries, CellUtil.createCellScanner(cells.iterator()),
247       replicationClusterId, baseNamespaceDir, hfileArchiveDir);
248     Scan scan = new Scan();
249     ResultScanner scanRes = table2.getScanner(scan);
250     for(Result res : scanRes) {
251       assertEquals(0, Bytes.toInt(res.getRow()) % 2);
252     }
253     scanRes = table1.getScanner(scan);
254     for(Result res : scanRes) {
255       assertEquals(1, Bytes.toInt(res.getRow()) % 2);
256     }
257   }
258 
259   /**
260    * Insert then do different types of deletes
261    * @throws Exception
262    */
263   @Test
264   public void testMixedDeletes() throws Exception {
265     List<WALEntry> entries = new ArrayList<WALEntry>(3);
266     List<Cell> cells = new ArrayList<Cell>();
267     for(int i = 0; i < 3; i++) {
268       entries.add(createEntry(TABLE_NAME1, i, KeyValue.Type.Put, cells));
269     }
270     SINK.replicateEntries(entries, CellUtil.createCellScanner(cells.iterator()),
271       replicationClusterId, baseNamespaceDir, hfileArchiveDir);
272     entries = new ArrayList<WALEntry>(3);
273     cells = new ArrayList<Cell>();
274     entries.add(createEntry(TABLE_NAME1, 0, KeyValue.Type.DeleteColumn, cells));
275     entries.add(createEntry(TABLE_NAME1, 1, KeyValue.Type.DeleteFamily, cells));
276     entries.add(createEntry(TABLE_NAME1, 2, KeyValue.Type.DeleteColumn, cells));
277 
278     SINK.replicateEntries(entries, CellUtil.createCellScanner(cells.iterator()),
279       replicationClusterId, baseNamespaceDir, hfileArchiveDir);
280 
281     Scan scan = new Scan();
282     ResultScanner scanRes = table1.getScanner(scan);
283     assertEquals(0, scanRes.next(3).length);
284   }
285 
286   /**
287    * Puts are buffered, but this tests when a delete (not-buffered) is applied
288    * before the actual Put that creates it.
289    * @throws Exception
290    */
291   @Test
292   public void testApplyDeleteBeforePut() throws Exception {
293     List<WALEntry> entries = new ArrayList<WALEntry>(5);
294     List<Cell> cells = new ArrayList<Cell>();
295     for(int i = 0; i < 2; i++) {
296       entries.add(createEntry(TABLE_NAME1, i, KeyValue.Type.Put, cells));
297     }
298     entries.add(createEntry(TABLE_NAME1, 1, KeyValue.Type.DeleteFamily, cells));
299     for(int i = 3; i < 5; i++) {
300       entries.add(createEntry(TABLE_NAME1, i, KeyValue.Type.Put, cells));
301     }
302     SINK.replicateEntries(entries, CellUtil.createCellScanner(cells.iterator()),
303       replicationClusterId, baseNamespaceDir, hfileArchiveDir);
304     Get get = new Get(Bytes.toBytes(1));
305     Result res = table1.get(get);
306     assertEquals(0, res.size());
307   }
308 
309   @Test
310   public void testRethrowRetriesExhaustedWithDetailsException() throws Exception {
311     TableName notExistTable = TableName.valueOf("notExistTable");
312     List<WALEntry> entries = new ArrayList<>();
313     List<Cell> cells = new ArrayList<>();
314     for (int i = 0; i < 10; i++) {
315       entries.add(createEntry(notExistTable, i, KeyValue.Type.Put, cells));
316     }
317     try {
318       SINK.replicateEntries(entries, CellUtil.createCellScanner(cells.iterator()),
319         replicationClusterId, baseNamespaceDir, hfileArchiveDir);
320       Assert.fail("Should re-throw TableNotFoundException.");
321     } catch (TableNotFoundException e) {
322     }
323     entries.clear();
324     cells.clear();
325     for (int i = 0; i < 10; i++) {
326       entries.add(createEntry(TABLE_NAME1, i, KeyValue.Type.Put, cells));
327     }
328     try (Connection conn = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration())) {
329       try (Admin admin = conn.getAdmin()) {
330         admin.disableTable(TABLE_NAME1);
331         try {
332           SINK.replicateEntries(entries, CellUtil.createCellScanner(cells.iterator()),
333             replicationClusterId, baseNamespaceDir, hfileArchiveDir);
334           Assert.fail("Should re-throw RetriesExhaustedWithDetailsException.");
335         } catch (RetriesExhaustedWithDetailsException e) {
336         } finally {
337           admin.enableTable(TABLE_NAME1);
338         }
339       }
340     }
341   }
342 
343   /**
344    * Test replicateEntries with a bulk load entry for 25 HFiles
345    */
346   @Test
347   public void testReplicateEntriesForHFiles() throws Exception {
348     Path dir = TEST_UTIL.getDataTestDirOnTestFS("testReplicateEntries");
349     Path familyDir = new Path(dir, Bytes.toString(FAM_NAME1));
350     int numRows = 10;
351 
352     List<Path> p = new ArrayList<>(1);
353 
354     // 1. Generate 25 hfile ranges
355     Random rng = new SecureRandom();
356     Set<Integer> numbers = new HashSet<>();
357     while (numbers.size() < 50) {
358       numbers.add(rng.nextInt(1000));
359     }
360     List<Integer> numberList = new ArrayList<>(numbers);
361     Collections.sort(numberList);
362     Map<String, Long> storeFilesSize = new HashMap<String, Long>(1);
363 
364     // 2. Create 25 hfiles
365     Configuration conf = TEST_UTIL.getConfiguration();
366     FileSystem fs = dir.getFileSystem(conf);
367     Iterator<Integer> numbersItr = numberList.iterator();
368     for (int i = 0; i < 25; i++) {
369       Path hfilePath = new Path(familyDir, "hfile_" + i);
370       HFileTestUtil.createHFile(conf, fs, hfilePath, FAM_NAME1, FAM_NAME1,
371         Bytes.toBytes(numbersItr.next()), Bytes.toBytes(numbersItr.next()), numRows);
372       p.add(hfilePath);
373       storeFilesSize.put(hfilePath.getName(), fs.getFileStatus(hfilePath).getLen());
374     }
375 
376     // 3. Create a BulkLoadDescriptor and a WALEdit
377     Map<byte[], List<Path>> storeFiles = new HashMap<>(1);
378     storeFiles.put(FAM_NAME1, p);
379     WALEdit edit = null;
380     WALProtos.BulkLoadDescriptor loadDescriptor = null;
381 
382     try (Connection c = ConnectionFactory.createConnection(conf);
383         RegionLocator l = c.getRegionLocator(TABLE_NAME1)) {
384       HRegionInfo regionInfo = l.getAllRegionLocations().get(0).getRegionInfo();
385       loadDescriptor =
386           ProtobufUtil.toBulkLoadDescriptor(TABLE_NAME1,
387             ByteStringer.wrap(regionInfo.getEncodedNameAsBytes()), storeFiles, storeFilesSize, 1);
388       edit = WALEdit.createBulkLoadEvent(regionInfo, loadDescriptor);
389     }
390     List<WALEntry> entries = new ArrayList<WALEntry>(1);
391 
392     // 4. Create a WALEntryBuilder
393     WALEntry.Builder builder = createWALEntryBuilder(TABLE_NAME1);
394 
395     // 5. Copy the hfile to the path as it is in reality
396     for (int i = 0; i < 25; i++) {
397       String pathToHfileFromNS =
398           new StringBuilder(100).append(TABLE_NAME1.getNamespaceAsString()).append(Path.SEPARATOR)
399               .append(Bytes.toString(TABLE_NAME1.getName())).append(Path.SEPARATOR)
400               .append(Bytes.toString(loadDescriptor.getEncodedRegionName().toByteArray()))
401               .append(Path.SEPARATOR).append(Bytes.toString(FAM_NAME1)).append(Path.SEPARATOR)
402               .append("hfile_" + i).toString();
403       String dst = baseNamespaceDir + Path.SEPARATOR + pathToHfileFromNS;
404 
405       FileUtil.copy(fs, p.get(0), fs, new Path(dst), false, conf);
406     }
407 
408     entries.add(builder.build());
409     try (ResultScanner scanner = table1.getScanner(new Scan())) {
410       // 6. Assert no existing data in table
411       assertEquals(0, scanner.next(numRows).length);
412     }
413     // 7. Replicate the bulk loaded entry
414     SINK.replicateEntries(entries, CellUtil.createCellScanner(edit.getCells().iterator()),
415       replicationClusterId, baseNamespaceDir, hfileArchiveDir);
416     try (ResultScanner scanner = table1.getScanner(new Scan())) {
417       // 8. Assert data is replicated
418       assertEquals(numRows, scanner.next(numRows).length);
419     }
420   }
421 
422   private WALEntry createEntry(TableName table, int row,  KeyValue.Type type, List<Cell> cells) {
423     byte[] fam = table.equals(TABLE_NAME1) ? FAM_NAME1 : FAM_NAME2;
424     byte[] rowBytes = Bytes.toBytes(row);
425     // Just make sure we don't get the same ts for two consecutive rows with
426     // same key
427     try {
428       Thread.sleep(1);
429     } catch (InterruptedException e) {
430       LOG.info("Was interrupted while sleep, meh", e);
431     }
432     final long now = System.currentTimeMillis();
433     KeyValue kv = null;
434     if(type.getCode() == KeyValue.Type.Put.getCode()) {
435       kv = new KeyValue(rowBytes, fam, fam, now,
436           KeyValue.Type.Put, Bytes.toBytes(row));
437     } else if (type.getCode() == KeyValue.Type.DeleteColumn.getCode()) {
438         kv = new KeyValue(rowBytes, fam, fam,
439             now, KeyValue.Type.DeleteColumn);
440     } else if (type.getCode() == KeyValue.Type.DeleteFamily.getCode()) {
441         kv = new KeyValue(rowBytes, fam, null,
442             now, KeyValue.Type.DeleteFamily);
443     }
444     WALEntry.Builder builder = createWALEntryBuilder(table);
445     cells.add(kv);
446 
447     return builder.build();
448   }
449 
450   private WALEntry.Builder createWALEntryBuilder(TableName table) {
451     WALEntry.Builder builder = WALEntry.newBuilder();
452     builder.setAssociatedCellCount(1);
453     WALKey.Builder keyBuilder = WALKey.newBuilder();
454     UUID.Builder uuidBuilder = UUID.newBuilder();
455     uuidBuilder.setLeastSigBits(HConstants.DEFAULT_CLUSTER_ID.getLeastSignificantBits());
456     uuidBuilder.setMostSigBits(HConstants.DEFAULT_CLUSTER_ID.getMostSignificantBits());
457     keyBuilder.setClusterId(uuidBuilder.build());
458     keyBuilder.setTableName(ByteStringer.wrap(table.getName()));
459     keyBuilder.setWriteTime(System.currentTimeMillis());
460     keyBuilder.setEncodedRegionName(ByteStringer.wrap(HConstants.EMPTY_BYTE_ARRAY));
461     keyBuilder.setLogSequenceNumber(-1);
462     builder.setKey(keyBuilder.build());
463     return builder;
464   }
465 }