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.master.normalizer;
20  
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertFalse;
23  import static org.junit.Assert.assertNotNull;
24  import static org.junit.Assert.assertTrue;
25  import java.io.IOException;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Collections;
29  import java.util.Comparator;
30  import java.util.List;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.hadoop.hbase.CoordinatedStateException;
34  import org.apache.hadoop.hbase.HBaseTestingUtility;
35  import org.apache.hadoop.hbase.HConstants;
36  import org.apache.hadoop.hbase.HRegionInfo;
37  import org.apache.hadoop.hbase.HTableDescriptor;
38  import org.apache.hadoop.hbase.MetaTableAccessor;
39  import org.apache.hadoop.hbase.RegionLoad;
40  import org.apache.hadoop.hbase.ServerLoad;
41  import org.apache.hadoop.hbase.ServerName;
42  import org.apache.hadoop.hbase.TableName;
43  import org.apache.hadoop.hbase.Waiter;
44  import org.apache.hadoop.hbase.client.Admin;
45  import org.apache.hadoop.hbase.client.HTable;
46  import org.apache.hadoop.hbase.client.Put;
47  import org.apache.hadoop.hbase.master.HMaster;
48  import org.apache.hadoop.hbase.master.MasterServices;
49  import org.apache.hadoop.hbase.quotas.QuotaUtil;
50  import org.apache.hadoop.hbase.regionserver.HRegion;
51  import org.apache.hadoop.hbase.regionserver.Region;
52  import org.apache.hadoop.hbase.testclassification.MediumTests;
53  import org.apache.hadoop.hbase.util.Bytes;
54  import org.apache.hadoop.hbase.util.LoadTestKVGenerator;
55  import org.junit.AfterClass;
56  import org.junit.Before;
57  import org.junit.BeforeClass;
58  import org.junit.Rule;
59  import org.junit.Test;
60  import org.junit.experimental.categories.Category;
61  import org.junit.rules.TestName;
62  
63  /**
64   * Testing {@link SimpleRegionNormalizer} on minicluster.
65   */
66  @Category(MediumTests.class)
67  public class TestSimpleRegionNormalizerOnCluster {
68    private static final Log LOG = LogFactory.getLog(TestSimpleRegionNormalizerOnCluster.class);
69    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
70    private static final byte[] FAMILYNAME = Bytes.toBytes("fam");
71    private static Admin admin;
72    private static HMaster master;
73  
74    @Rule
75    public TestName name = new TestName();
76  
77    @BeforeClass
78    public static void beforeAllTests() throws Exception {
79      // we will retry operations when PleaseHoldException is thrown
80      TEST_UTIL.getConfiguration().setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 3);
81      TEST_UTIL.getConfiguration().setBoolean(QuotaUtil.QUOTA_CONF_KEY, true);
82  
83      // no way for the test to set the regionId on a created region, so disable this feature.
84      TEST_UTIL.getConfiguration().setInt("hbase.normalizer.merge.min_region_age.days", 0);
85  
86      // Start a cluster of two regionservers.
87      TEST_UTIL.startMiniCluster(1);
88      //TestNamespaceAuditor.waitForQuotaEnabled();
89      admin = TEST_UTIL.getHBaseAdmin();
90      master = TEST_UTIL.getHBaseCluster().getMaster();
91      assertNotNull(master);
92    }
93  
94    @AfterClass
95    public static void afterAllTests() throws Exception {
96      TEST_UTIL.shutdownMiniCluster();
97    }
98  
99    @Before
100   public void before() throws IOException {
101     // disable the normalizer ahead of time, let the test enable it when its ready.
102     admin.setNormalizerRunning(false);
103   }
104 
105   @Test
106   public void testHonorsNormalizerSwitch() throws IOException {
107     assertFalse(admin.isNormalizerEnabled());
108     assertFalse(admin.normalize());
109     assertFalse(admin.setNormalizerRunning(true));
110     assertTrue(admin.normalize());
111   }
112 
113 
114   @Test(timeout = 60000)
115   @SuppressWarnings("deprecation")
116   public void testRegionNormalizationSplitOnCluster() throws Exception {
117     final TableName TABLENAME = TableName.valueOf(name.getMethodName());
118 
119     try (HTable ht = TEST_UTIL.createMultiRegionTable(TABLENAME, FAMILYNAME, 5)) {
120       // Need to get sorted list of regions here
121       List<HRegion> generatedRegions = TEST_UTIL.getHBaseCluster().getRegions(TABLENAME);
122       Collections.sort(generatedRegions, new Comparator<HRegion>() {
123         @Override
124         public int compare(HRegion o1, HRegion o2) {
125           return o1.getRegionInfo().compareTo(o2.getRegionInfo());
126         }
127       });
128 
129       HRegion region = generatedRegions.get(0);
130       generateTestData(region, 1);
131       region.flush(true);
132 
133       region = generatedRegions.get(1);
134       generateTestData(region, 1);
135       region.flush(true);
136 
137       region = generatedRegions.get(2);
138       generateTestData(region, 2);
139       region.flush(true);
140 
141       region = generatedRegions.get(3);
142       generateTestData(region, 2);
143       region.flush(true);
144 
145       region = generatedRegions.get(4);
146       generateTestData(region, 5);
147       region.flush(true);
148 
149     }
150 
151     HTableDescriptor htd = admin.getTableDescriptor(TABLENAME);
152     htd.setNormalizationEnabled(true);
153     admin.modifyTable(TABLENAME, htd);
154 
155     admin.flush(TABLENAME);
156     admin.setNormalizerRunning(true);
157 
158     System.out.println(admin.getTableDescriptor(TABLENAME));
159 
160     assertEquals(5, MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), TABLENAME));
161 
162     // Now trigger a split and stop when the split is in progress
163     Thread.sleep(5000); // to let region load to update
164     boolean b = master.normalizeRegions();
165     assertTrue(b);
166 
167     while (true) {
168       List<HRegion> regions = TEST_UTIL.getHBaseCluster().getRegions(TABLENAME);
169       int cnt = 0;
170       for (HRegion region : regions) {
171         String regionName = region.getRegionInfo().getRegionNameAsString();
172         if (regionName.startsWith("testRegionNormalizationSplitOnCluster,zzzzz")) {
173           cnt++;
174         }
175       }
176       if (cnt >= 2) {
177         break;
178       }
179     }
180     admin.disableTable(TABLENAME);
181     admin.deleteTable(TABLENAME);
182   }
183 
184   @Test(timeout = 60000)
185   @SuppressWarnings("deprecation")
186   public void testRegionNormalizationMergeOnCluster() throws Exception {
187     final TableName TABLENAME = TableName.valueOf(name.getMethodName());
188 
189     // create 5 regions with sizes to trigger merge of small regions
190     createTable(TABLENAME, Arrays.asList(1, 1, 3, 3, 5));
191 
192     HTableDescriptor htd = admin.getTableDescriptor(TABLENAME);
193     htd.setNormalizationEnabled(true);
194     admin.modifyTable(TABLENAME, htd);
195 
196     admin.flush(TABLENAME);
197 
198     assertEquals(5, MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), TABLENAME));
199 
200     // Now trigger a merge and stop when the merge is in progress
201     admin.setNormalizerRunning(true);
202     Thread.sleep(5000); // to let region load to update
203     master.normalizeRegions();
204 
205     while (MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), TABLENAME) > 4) {
206       LOG.info("Waiting for normalization merge to complete");
207       Thread.sleep(100);
208     }
209 
210     assertEquals(4, MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), TABLENAME));
211     dropIfExists(TABLENAME);
212   }
213 
214   @Test(timeout = 60000)
215   public void testMultiTablePlans()
216       throws IOException, InterruptedException, CoordinatedStateException {
217     // create 3 tables with regions to be normalized
218     int numOfTables = 3;
219     String methodName = name.getMethodName();
220     List<TableName> tableNames = new ArrayList<>();
221     // init names
222     for (int i = 0; i < numOfTables; i++) {
223       tableNames.add(TableName.valueOf(methodName + i));
224     }
225     createTable(tableNames.get(0), Arrays.asList(1, 1, 3, 5, 3));
226     createTable(tableNames.get(1), Arrays.asList(1, 1, 1, 1, 1));
227     createTable(tableNames.get(2), Arrays.asList(1, 1, 3, 5, 3));
228 
229     // enable region normalizer for all above tables
230     for (int i = 0; i < numOfTables; i++) {
231       HTableDescriptor htd = admin.getTableDescriptor(tableNames.get(i));
232       htd.setNormalizationEnabled(true);
233       admin.modifyTable(tableNames.get(i), htd);
234     }
235     for (int i = 0; i < numOfTables; i++) {
236       // check region has been created properly
237       assertEquals(5,
238         MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), tableNames.get(i)));
239     }
240     admin.setNormalizerRunning(true);
241     Thread.sleep(5000); // to let region load to update
242     boolean checkStatus = master.normalizeRegions();
243     assertTrue(checkStatus);
244     // this sleeps helps in getting accurate result from MetaTableAccessor.getRegionCount
245     Thread.sleep(5000);
246     // something is miss in test. If this test passes that doesnot mean correctness
247     // but this test failing means there is surely an issue. It happens because the order of table
248     // normalization is not fix so there are chances that the second table is finished in the end
249     // which means test will pass even though there is an issue in normalizer.
250     // Culprit is HMaster code where we call Collections.shuffle(allEnabledTables)
251     assertEquals(4, MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), tableNames.get(0)));
252     assertEquals(5, MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), tableNames.get(1)));
253     assertEquals(4, MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), tableNames.get(2)));
254     for (int i = 0; i < numOfTables; i++) {
255       dropIfExists(tableNames.get(i));
256     }
257   }
258 
259   private void createTable(TableName TABLENAME, List<Integer> rowsInRespectiveRegions)
260       throws IOException {
261     int numOfRegions = rowsInRespectiveRegions.size();
262     try (HTable ht = TEST_UTIL.createMultiRegionTable(TABLENAME, FAMILYNAME, numOfRegions)) {
263       // Need to get sorted list of regions here
264       List<HRegion> generatedRegions = TEST_UTIL.getHBaseCluster().getRegions(TABLENAME);
265       Collections.sort(generatedRegions, new Comparator<HRegion>() {
266         @Override
267         public int compare(HRegion o1, HRegion o2) {
268           return o1.getRegionInfo().compareTo(o2.getRegionInfo());
269         }
270       });
271       for (int i = 0; i < numOfRegions; i++) {
272         HRegion region = generatedRegions.get(i);
273         generateTestData(region, rowsInRespectiveRegions.get(i));
274         region.flush(true);
275       }
276     }
277   }
278 
279   private static void waitForTableSplit(final TableName tableName, final int targetRegionCount)
280     throws IOException {
281     TEST_UTIL.waitFor(10*1000, new Waiter.ExplainingPredicate<IOException>() {
282       @Override public String explainFailure() {
283         return "expected normalizer to split region.";
284       }
285       @Override public boolean evaluate() throws IOException {
286         final int currentRegionCount =
287           MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), tableName);
288         return currentRegionCount >= targetRegionCount;
289       }
290     });
291   }
292 
293   private static List<HRegion> generateTestData(final TableName tableName,
294                                                 final int... regionSizesMb) throws IOException {
295     final List<HRegion> generatedRegions;
296     final int numRegions = regionSizesMb.length;
297     try (HTable ignored = TEST_UTIL.createMultiRegionTable(tableName, FAMILYNAME, numRegions)) {
298       // Need to get sorted list of regions here
299       generatedRegions = TEST_UTIL.getHBaseCluster().getRegions(tableName);
300       //generatedRegions.sort(Comparator.comparing(HRegion::getRegionInfo, RegionInfo.COMPARATOR));
301       Collections.sort(generatedRegions, new Comparator<HRegion>() {
302         @Override
303         public int compare(HRegion o1, HRegion o2) {
304           return o1.getRegionInfo().compareTo(o2.getRegionInfo());
305         }
306       });
307       assertEquals(numRegions, generatedRegions.size());
308       for (int i = 0; i < numRegions; i++) {
309         HRegion region = generatedRegions.get(i);
310         generateTestData(region, regionSizesMb[i]);
311         region.flush(true);
312       }
313     }
314     return generatedRegions;
315   }
316 
317   private static void generateTestData(Region region, int numRows) throws IOException {
318     // generating 1Mb values
319     LoadTestKVGenerator dataGenerator = new LoadTestKVGenerator(1024 * 1024, 1024 * 1024);
320     for (int i = 0; i < numRows; ++i) {
321       byte[] key = Bytes.add(region.getRegionInfo().getStartKey(), Bytes.toBytes(i));
322       for (int j = 0; j < 1; ++j) {
323         Put put = new Put(key);
324         byte[] col = Bytes.toBytes(String.valueOf(j));
325         byte[] value = dataGenerator.generateRandomSizeValue(key, col);
326         put.add(FAMILYNAME, col, value);
327         region.put(put);
328       }
329     }
330   }
331 
332   private static double getRegionSizeMB(final MasterServices masterServices,
333                                         final HRegionInfo regionInfo) {
334     ServerName sn =
335       masterServices.getAssignmentManager().getRegionStates().getRegionServerOfRegion(regionInfo);
336     if (sn == null) {
337       LOG.debug(regionInfo.getRegionNameAsString() + " region was not found on any Server");
338       return -1;
339     }
340     ServerLoad load = masterServices.getServerManager().getLoad(sn);
341     if (load == null) {
342       LOG.debug(sn.getServerName() + " was not found in online servers");
343       return -1;
344     }
345     RegionLoad regionLoad = load.getRegionsLoad().get(regionInfo.getRegionName());
346     if (regionLoad == null) {
347       LOG.debug(regionInfo.getRegionNameAsString() + " was not found in RegionsLoad");
348       return -1;
349     }
350     return regionLoad.getStorefileSizeMB();
351   }
352 
353 
354   private static void dropIfExists(final TableName tableName) throws IOException {
355     if (tableName != null && admin.tableExists(tableName)) {
356       if (admin.isTableEnabled(tableName)) {
357         admin.disableTable(tableName);
358       }
359       admin.deleteTable(tableName);
360     }
361   }
362 }