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