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  
19  package org.apache.hadoop.hbase.regionserver;
20  
21  import static org.apache.hadoop.hbase.regionserver.TestRegionServerNoMaster.*;
22  import java.io.IOException;
23  import java.util.Random;
24  import java.util.concurrent.ExecutorService;
25  import java.util.concurrent.Executors;
26  import java.util.concurrent.TimeUnit;
27  import java.util.concurrent.atomic.AtomicBoolean;
28  import java.util.concurrent.atomic.AtomicReference;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.hbase.Cell;
33  import org.apache.hadoop.hbase.HBaseTestingUtility;
34  import org.apache.hadoop.hbase.HConstants;
35  import org.apache.hadoop.hbase.HRegionInfo;
36  import org.apache.hadoop.hbase.testclassification.MediumTests;
37  import org.apache.hadoop.hbase.TableName;
38  import org.apache.hadoop.hbase.TestMetaTableAccessor;
39  import org.apache.hadoop.hbase.client.Consistency;
40  import org.apache.hadoop.hbase.client.Get;
41  import org.apache.hadoop.hbase.client.HTable;
42  import org.apache.hadoop.hbase.client.Put;
43  import org.apache.hadoop.hbase.client.Result;
44  import org.apache.hadoop.hbase.client.Table;
45  import org.apache.hadoop.hbase.io.hfile.HFileScanner;
46  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
47  import org.apache.hadoop.hbase.protobuf.RequestConverter;
48  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos;
49  import org.apache.hadoop.hbase.util.Bytes;
50  import org.apache.hadoop.hbase.util.Threads;
51  import org.apache.hadoop.hbase.zookeeper.ZKAssign;
52  import org.apache.hadoop.hdfs.DFSConfigKeys;
53  import org.apache.hadoop.util.StringUtils;
54  import org.junit.After;
55  import org.junit.AfterClass;
56  import org.junit.Assert;
57  import org.junit.BeforeClass;
58  import org.junit.Test;
59  import org.junit.experimental.categories.Category;
60  
61  import com.google.protobuf.ServiceException;
62  
63  /**
64   * Tests for region replicas. Sad that we cannot isolate these without bringing up a whole
65   * cluster. See {@link TestRegionServerNoMaster}.
66   */
67  @Category(MediumTests.class)
68  public class TestRegionReplicas {
69    private static final Log LOG = LogFactory.getLog(TestRegionReplicas.class);
70  
71    private static final int NB_SERVERS = 1;
72    private static HTable table;
73    private static final byte[] row = "TestRegionReplicas".getBytes();
74  
75    private static HRegionInfo hriPrimary;
76    private static HRegionInfo hriSecondary;
77  
78    private static final HBaseTestingUtility HTU = new HBaseTestingUtility();
79    private static final byte[] f = HConstants.CATALOG_FAMILY;
80  
81    @BeforeClass
82    public static void before() throws Exception {
83      // Reduce the hdfs block size and prefetch to trigger the file-link reopen
84      // when the file is moved to archive (e.g. compaction)
85      HTU.getConfiguration().setInt(DFSConfigKeys.DFS_BLOCK_SIZE_KEY, 8192);
86      HTU.getConfiguration().setInt(DFSConfigKeys.DFS_CLIENT_READ_PREFETCH_SIZE_KEY, 1);
87      HTU.getConfiguration().setInt(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, 128 * 1024 * 1024);
88  
89      HTU.startMiniCluster(NB_SERVERS);
90      final TableName tableName = TableName.valueOf(TestRegionReplicas.class.getSimpleName());
91  
92      // Create table then get the single region for our new table.
93      table = HTU.createTable(tableName, f);
94  
95      hriPrimary = table.getRegionLocation(row, false).getRegionInfo();
96  
97      // mock a secondary region info to open
98      hriSecondary = new HRegionInfo(hriPrimary.getTable(), hriPrimary.getStartKey(),
99          hriPrimary.getEndKey(), hriPrimary.isSplit(), hriPrimary.getRegionId(), 1);
100 
101     // No master
102     TestRegionServerNoMaster.stopMasterAndAssignMeta(HTU);
103   }
104 
105   @AfterClass
106   public static void afterClass() throws Exception {
107     table.close();
108     HTU.shutdownMiniCluster();
109   }
110 
111   @After
112   public void after() throws Exception {
113     // Clean the state if the test failed before cleaning the znode
114     // It does not manage all bad failures, so if there are multiple failures, only
115     //  the first one should be looked at.
116     ZKAssign.deleteNodeFailSilent(HTU.getZooKeeperWatcher(), hriPrimary);
117   }
118 
119   private HRegionServer getRS() {
120     return HTU.getMiniHBaseCluster().getRegionServer(0);
121   }
122 
123   @Test(timeout = 60000)
124   public void testOpenRegionReplica() throws Exception {
125     openRegion(HTU, getRS(), hriSecondary);
126     try {
127       //load some data to primary
128       HTU.loadNumericRows(table, f, 0, 1000);
129 
130       // assert that we can read back from primary
131       Assert.assertEquals(1000, HTU.countRows(table));
132     } finally {
133       HTU.deleteNumericRows(table, f, 0, 1000);
134       closeRegion(HTU, getRS(), hriSecondary);
135     }
136   }
137 
138   /** Tests that the meta location is saved for secondary regions */
139   @Test(timeout = 60000)
140   public void testRegionReplicaUpdatesMetaLocation() throws Exception {
141     openRegion(HTU, getRS(), hriSecondary);
142     Table meta = null;
143     try {
144       meta = HTU.getConnection().getTable(TableName.META_TABLE_NAME);
145       TestMetaTableAccessor.assertMetaLocation(meta, hriPrimary.getRegionName()
146         , getRS().getServerName(), -1, 1, false);
147     } finally {
148       if (meta != null ) meta.close();
149       closeRegion(HTU, getRS(), hriSecondary);
150     }
151   }
152 
153   @Test(timeout = 60000)
154   public void testRegionReplicaGets() throws Exception {
155     try {
156       //load some data to primary
157       HTU.loadNumericRows(table, f, 0, 1000);
158       // assert that we can read back from primary
159       Assert.assertEquals(1000, HTU.countRows(table));
160       // flush so that region replica can read
161       Region region = getRS().getRegionByEncodedName(hriPrimary.getEncodedName());
162       region.flush(true);
163 
164       openRegion(HTU, getRS(), hriSecondary);
165 
166       // first try directly against region
167       region = getRS().getFromOnlineRegions(hriSecondary.getEncodedName());
168       assertGet(region, 42, true);
169 
170       assertGetRpc(hriSecondary, 42, true);
171     } finally {
172       HTU.deleteNumericRows(table, HConstants.CATALOG_FAMILY, 0, 1000);
173       closeRegion(HTU, getRS(), hriSecondary);
174     }
175   }
176 
177   @Test(timeout = 60000)
178   public void testGetOnTargetRegionReplica() throws Exception {
179     try {
180       //load some data to primary
181       HTU.loadNumericRows(table, f, 0, 1000);
182       // assert that we can read back from primary
183       Assert.assertEquals(1000, HTU.countRows(table));
184       // flush so that region replica can read
185       Region region = getRS().getRegionByEncodedName(hriPrimary.getEncodedName());
186       region.flush(true);
187 
188       openRegion(HTU, getRS(), hriSecondary);
189       // evict connection cache  since we have manually deployed hriSecondary after HTU.countRows()
190       Thread.sleep(5000);
191       table.clearRegionCache();
192 
193       // try directly Get against region replica
194       byte[] row = Bytes.toBytes(String.valueOf(42));
195       Get get = new Get(row);
196       get.setConsistency(Consistency.TIMELINE);
197       get.setReplicaId(1);
198       Result result = table.get(get);
199       Assert.assertArrayEquals(row, result.getValue(f, null));
200     } finally {
201       HTU.deleteNumericRows(table, HConstants.CATALOG_FAMILY, 0, 1000);
202       closeRegion(HTU, getRS(), hriSecondary);
203     }
204   }
205 
206   private void assertGet(Region region, int value, boolean expect) throws IOException {
207     byte[] row = Bytes.toBytes(String.valueOf(value));
208     Get get = new Get(row);
209     Result result = region.get(get);
210     if (expect) {
211       Assert.assertArrayEquals(row, result.getValue(f, null));
212     } else {
213       result.isEmpty();
214     }
215   }
216 
217   // build a mock rpc
218   private void assertGetRpc(HRegionInfo info, int value, boolean expect)
219       throws IOException, ServiceException {
220     byte[] row = Bytes.toBytes(String.valueOf(value));
221     Get get = new Get(row);
222     ClientProtos.GetRequest getReq = RequestConverter.buildGetRequest(info.getRegionName(), get);
223     ClientProtos.GetResponse getResp =  getRS().getRSRpcServices().get(null, getReq);
224     Result result = ProtobufUtil.toResult(getResp.getResult());
225     if (expect) {
226       Assert.assertArrayEquals(row, result.getValue(f, null));
227     } else {
228       result.isEmpty();
229     }
230   }
231 
232   private void restartRegionServer() throws Exception {
233     afterClass();
234     before();
235   }
236 
237   @Test(timeout = 300000)
238   public void testRefreshStoreFiles() throws Exception {
239     // enable store file refreshing
240     final int refreshPeriod = 2000; // 2 sec
241     HTU.getConfiguration().setInt("hbase.hstore.compactionThreshold", 100);
242     HTU.getConfiguration().setInt(StorefileRefresherChore.REGIONSERVER_STOREFILE_REFRESH_PERIOD,
243       refreshPeriod);
244     // restart the region server so that it starts the refresher chore
245     restartRegionServer();
246 
247     try {
248       LOG.info("Opening the secondary region " + hriSecondary.getEncodedName());
249       openRegion(HTU, getRS(), hriSecondary);
250 
251       //load some data to primary
252       LOG.info("Loading data to primary region");
253       HTU.loadNumericRows(table, f, 0, 1000);
254       // assert that we can read back from primary
255       Assert.assertEquals(1000, HTU.countRows(table));
256       // flush so that region replica can read
257       LOG.info("Flushing primary region");
258       Region region = getRS().getRegionByEncodedName(hriPrimary.getEncodedName());
259       region.flush(true);
260 
261       // ensure that chore is run
262       LOG.info("Sleeping for " + (4 * refreshPeriod));
263       Threads.sleep(4 * refreshPeriod);
264 
265       LOG.info("Checking results from secondary region replica");
266       Region secondaryRegion = getRS().getFromOnlineRegions(hriSecondary.getEncodedName());
267       Assert.assertEquals(1, secondaryRegion.getStore(f).getStorefilesCount());
268 
269       assertGet(secondaryRegion, 42, true);
270       assertGetRpc(hriSecondary, 42, true);
271       assertGetRpc(hriSecondary, 1042, false);
272 
273       //load some data to primary
274       HTU.loadNumericRows(table, f, 1000, 1100);
275       region = getRS().getRegionByEncodedName(hriPrimary.getEncodedName());
276       region.flush(true);
277 
278       HTU.loadNumericRows(table, f, 2000, 2100);
279       region = getRS().getRegionByEncodedName(hriPrimary.getEncodedName());
280       region.flush(true);
281 
282       // ensure that chore is run
283       Threads.sleep(4 * refreshPeriod);
284 
285       assertGetRpc(hriSecondary, 42, true);
286       assertGetRpc(hriSecondary, 1042, true);
287       assertGetRpc(hriSecondary, 2042, true);
288 
289       // ensure that we are see the 3 store files
290       Assert.assertEquals(3, secondaryRegion.getStore(f).getStorefilesCount());
291 
292       // force compaction
293       HTU.compact(table.getName(), true);
294 
295       long wakeUpTime = System.currentTimeMillis() + 4 * refreshPeriod;
296       while (System.currentTimeMillis() < wakeUpTime) {
297         assertGetRpc(hriSecondary, 42, true);
298         assertGetRpc(hriSecondary, 1042, true);
299         assertGetRpc(hriSecondary, 2042, true);
300         Threads.sleep(10);
301       }
302 
303       // ensure that we see the compacted file only
304       Assert.assertEquals(1, secondaryRegion.getStore(f).getStorefilesCount());
305 
306     } finally {
307       HTU.deleteNumericRows(table, HConstants.CATALOG_FAMILY, 0, 1000);
308       closeRegion(HTU, getRS(), hriSecondary);
309     }
310   }
311 
312   @Test(timeout = 300000)
313   public void testFlushAndCompactionsInPrimary() throws Exception {
314 
315     long runtime = 30 * 1000;
316     // enable store file refreshing
317     final int refreshPeriod = 100; // 100ms refresh is a lot
318     HTU.getConfiguration().setInt("hbase.hstore.compactionThreshold", 3);
319     HTU.getConfiguration().setInt(StorefileRefresherChore.REGIONSERVER_STOREFILE_REFRESH_PERIOD, refreshPeriod);
320     // restart the region server so that it starts the refresher chore
321     restartRegionServer();
322     final int startKey = 0, endKey = 1000;
323 
324     try {
325       openRegion(HTU, getRS(), hriSecondary);
326 
327       //load some data to primary so that reader won't fail
328       HTU.loadNumericRows(table, f, startKey, endKey);
329       TestRegionServerNoMaster.flushRegion(HTU, hriPrimary);
330       // ensure that chore is run
331       Threads.sleep(2 * refreshPeriod);
332 
333       final AtomicBoolean running = new AtomicBoolean(true);
334       @SuppressWarnings("unchecked")
335       final AtomicReference<Exception>[] exceptions = new AtomicReference[3];
336       for (int i=0; i < exceptions.length; i++) {
337         exceptions[i] = new AtomicReference<Exception>();
338       }
339 
340       Runnable writer = new Runnable() {
341         int key = startKey;
342         @Override
343         public void run() {
344           try {
345             while (running.get()) {
346               byte[] data = Bytes.toBytes(String.valueOf(key));
347               Put put = new Put(data);
348               put.add(f, null, data);
349               table.put(put);
350               key++;
351               if (key == endKey) key = startKey;
352             }
353           } catch (Exception ex) {
354             LOG.warn(ex);
355             exceptions[0].compareAndSet(null, ex);
356           }
357         }
358       };
359 
360       Runnable flusherCompactor = new Runnable() {
361         Random random = new Random();
362         @Override
363         public void run() {
364           try {
365             while (running.get()) {
366               // flush or compact
367               if (random.nextBoolean()) {
368                 TestRegionServerNoMaster.flushRegion(HTU, hriPrimary);
369               } else {
370                 HTU.compact(table.getName(), random.nextBoolean());
371               }
372             }
373           } catch (Exception ex) {
374             LOG.warn(ex);
375             exceptions[1].compareAndSet(null, ex);
376           }
377         }
378       };
379 
380       Runnable reader = new Runnable() {
381         Random random = new Random();
382         @Override
383         public void run() {
384           try {
385             while (running.get()) {
386               // whether to do a close and open
387               if (random.nextInt(10) == 0) {
388                 try {
389                   closeRegion(HTU, getRS(), hriSecondary);
390                 } catch (Exception ex) {
391                   LOG.warn("Failed closing the region " + hriSecondary + " "  + StringUtils.stringifyException(ex));
392                   exceptions[2].compareAndSet(null, ex);
393                 }
394                 try {
395                   openRegion(HTU, getRS(), hriSecondary);
396                 } catch (Exception ex) {
397                   LOG.warn("Failed opening the region " + hriSecondary + " "  + StringUtils.stringifyException(ex));
398                   exceptions[2].compareAndSet(null, ex);
399                 }
400               }
401 
402               int key = random.nextInt(endKey - startKey) + startKey;
403               assertGetRpc(hriSecondary, key, true);
404             }
405           } catch (Exception ex) {
406             LOG.warn("Failed getting the value in the region " + hriSecondary + " "  + StringUtils.stringifyException(ex));
407             exceptions[2].compareAndSet(null, ex);
408           }
409         }
410       };
411 
412       LOG.info("Starting writer and reader");
413       ExecutorService executor = Executors.newFixedThreadPool(3);
414       executor.submit(writer);
415       executor.submit(flusherCompactor);
416       executor.submit(reader);
417 
418       // wait for threads
419       Threads.sleep(runtime);
420       running.set(false);
421       executor.shutdown();
422       executor.awaitTermination(30, TimeUnit.SECONDS);
423 
424       for (AtomicReference<Exception> exRef : exceptions) {
425         Assert.assertNull(exRef.get());
426       }
427     } finally {
428       HTU.deleteNumericRows(table, HConstants.CATALOG_FAMILY, startKey, endKey);
429       closeRegion(HTU, getRS(), hriSecondary);
430     }
431   }
432 
433   @Test(timeout = 300000)
434   public void testVerifySecondaryAbilityToReadWithOnFiles() throws Exception {
435     // disable the store file refresh chore (we do this by hand)
436     HTU.getConfiguration().setInt(StorefileRefresherChore.REGIONSERVER_STOREFILE_REFRESH_PERIOD, 0);
437     restartRegionServer();
438 
439     try {
440       LOG.info("Opening the secondary region " + hriSecondary.getEncodedName());
441       openRegion(HTU, getRS(), hriSecondary);
442 
443       // load some data to primary
444       LOG.info("Loading data to primary region");
445       for (int i = 0; i < 3; ++i) {
446         HTU.loadNumericRows(table, f, i * 1000, (i + 1) * 1000);
447         Region region = getRS().getRegionByEncodedName(hriPrimary.getEncodedName());
448         region.flush(true);
449       }
450 
451       Region primaryRegion = getRS().getFromOnlineRegions(hriPrimary.getEncodedName());
452       Assert.assertEquals(3, primaryRegion.getStore(f).getStorefilesCount());
453 
454       // Refresh store files on the secondary
455       Region secondaryRegion = getRS().getFromOnlineRegions(hriSecondary.getEncodedName());
456       secondaryRegion.getStore(f).refreshStoreFiles();
457       Assert.assertEquals(3, secondaryRegion.getStore(f).getStorefilesCount());
458 
459       // force compaction
460       LOG.info("Force Major compaction on primary region " + hriPrimary);
461       primaryRegion.compact(true);
462       Assert.assertEquals(1, primaryRegion.getStore(f).getStorefilesCount());
463 
464       // scan all the hfiles on the secondary.
465       // since there are no read on the secondary when we ask locations to
466       // the NN a FileNotFound exception will be returned and the FileLink
467       // should be able to deal with it giving us all the result we expect.
468       int keys = 0;
469       int sum = 0;
470       for (StoreFile sf: secondaryRegion.getStore(f).getStorefiles()) {
471         // Our file does not exist anymore. was moved by the compaction above.
472         LOG.debug(getRS().getFileSystem().exists(sf.getPath()));
473         Assert.assertFalse(getRS().getFileSystem().exists(sf.getPath()));
474 
475         HFileScanner scanner = sf.getReader().getScanner(false, false);
476         scanner.seekTo();
477         do {
478           keys++;
479 
480           Cell cell = scanner.getKeyValue();
481           sum += Integer.parseInt(Bytes.toString(cell.getRowArray(),
482             cell.getRowOffset(), cell.getRowLength()));
483         } while (scanner.next());
484       }
485       Assert.assertEquals(3000, keys);
486       Assert.assertEquals(4498500, sum);
487     } finally {
488       HTU.deleteNumericRows(table, HConstants.CATALOG_FAMILY, 0, 1000);
489       closeRegion(HTU, getRS(), hriSecondary);
490     }
491   }
492 }