001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.master.janitor;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertNotNull;
023import static org.junit.Assert.assertTrue;
024
025import java.io.IOException;
026import java.util.Arrays;
027import java.util.LinkedList;
028import java.util.List;
029import org.apache.hadoop.hbase.HBaseClassTestRule;
030import org.apache.hadoop.hbase.HBaseTestingUtility;
031import org.apache.hadoop.hbase.HConstants;
032import org.apache.hadoop.hbase.MetaTableAccessor;
033import org.apache.hadoop.hbase.TableName;
034import org.apache.hadoop.hbase.client.Put;
035import org.apache.hadoop.hbase.client.RegionInfo;
036import org.apache.hadoop.hbase.client.RegionInfoBuilder;
037import org.apache.hadoop.hbase.master.assignment.RegionStateStore;
038import org.apache.hadoop.hbase.testclassification.LargeTests;
039import org.apache.hadoop.hbase.testclassification.MasterTests;
040import org.apache.hadoop.hbase.util.Bytes;
041import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
042import org.apache.hadoop.hbase.util.Pair;
043import org.junit.After;
044import org.junit.Before;
045import org.junit.ClassRule;
046import org.junit.Rule;
047import org.junit.Test;
048import org.junit.experimental.categories.Category;
049import org.junit.rules.TestName;
050import org.slf4j.Logger;
051import org.slf4j.LoggerFactory;
052
053@Category({ MasterTests.class, LargeTests.class })
054public class TestCatalogJanitorCluster {
055  private static final Logger LOG = LoggerFactory.getLogger(TestCatalogJanitorCluster.class);
056
057  @ClassRule
058  public static final HBaseClassTestRule CLASS_RULE =
059    HBaseClassTestRule.forClass(TestCatalogJanitorCluster.class);
060
061  @Rule
062  public final TestName name = new TestName();
063
064  private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
065  private static final TableName T1 = TableName.valueOf("t1");
066  private static final TableName T2 = TableName.valueOf("t2");
067  private static final TableName T3 = TableName.valueOf("t3");
068  private static final TableName T4 = TableName.valueOf("t4");
069  private static final TableName T5 = TableName.valueOf("t5");
070
071  @Before
072  public void before() throws Exception {
073    TEST_UTIL.startMiniCluster();
074    TEST_UTIL.createMultiRegionTable(T1, new byte[][] { HConstants.CATALOG_FAMILY });
075    TEST_UTIL.createMultiRegionTable(T2, new byte[][] { HConstants.CATALOG_FAMILY });
076    TEST_UTIL.createMultiRegionTable(T3, new byte[][] { HConstants.CATALOG_FAMILY });
077
078    final byte[][] keysForT4 =
079      { Bytes.toBytes("aa"), Bytes.toBytes("bb"), Bytes.toBytes("cc"), Bytes.toBytes("dd") };
080
081    TEST_UTIL.createTable(T4, HConstants.CATALOG_FAMILY, keysForT4);
082
083    final byte[][] keysForT5 = { Bytes.toBytes("bb"), Bytes.toBytes("cc"), Bytes.toBytes("dd") };
084
085    TEST_UTIL.createTable(T5, HConstants.CATALOG_FAMILY, keysForT5);
086  }
087
088  @After
089  public void after() throws Exception {
090    TEST_UTIL.shutdownMiniCluster();
091  }
092
093  /**
094   * Fat method where we start with a fat hbase:meta and then gradually intro problems running
095   * catalogjanitor for each to ensure it triggers complaint. Do one big method because takes a
096   * while to build up the context we need. We create three tables and then make holes, overlaps,
097   * add unknown servers and empty out regioninfo columns. Each should up counts in the
098   * CatalogJanitor.Report produced.
099   */
100  @Test
101  public void testConsistency() throws IOException {
102    CatalogJanitor janitor = TEST_UTIL.getHBaseCluster().getMaster().getCatalogJanitor();
103    RegionStateStore regionStateStore =
104      TEST_UTIL.getHBaseCluster().getMaster().getAssignmentManager().getRegionStateStore();
105    janitor.scan();
106    CatalogJanitorReport report = janitor.getLastReport();
107    // Assert no problems.
108    assertTrue(report.isEmpty());
109    // Now remove first region in table t2 to see if catalogjanitor scan notices.
110    List<RegionInfo> t2Ris = MetaTableAccessor.getTableRegions(TEST_UTIL.getConnection(), T2);
111    regionStateStore.deleteRegion(t2Ris.get(0));
112    janitor.scan();
113    report = janitor.getLastReport();
114    assertFalse(report.isEmpty());
115    assertEquals(1, report.getHoles().size());
116    assertTrue(report.getHoles().get(0).getFirst().getTable()
117      .equals(RegionInfoBuilder.UNDEFINED.getTable()));
118    assertTrue(report.getHoles().get(0).getSecond().getTable().equals(T2));
119    assertEquals(0, report.getOverlaps().size());
120    // Next, add overlaps to first row in t3
121    List<RegionInfo> t3Ris = MetaTableAccessor.getTableRegions(TEST_UTIL.getConnection(), T3);
122    RegionInfo ri = t3Ris.get(0);
123    RegionInfo newRi1 = RegionInfoBuilder.newBuilder(ri.getTable())
124      .setStartKey(incrementRow(ri.getStartKey())).setEndKey(incrementRow(ri.getEndKey())).build();
125    Put p1 = MetaTableAccessor.makePutFromRegionInfo(newRi1, EnvironmentEdgeManager.currentTime());
126    RegionInfo newRi2 = RegionInfoBuilder.newBuilder(newRi1.getTable())
127      .setStartKey(incrementRow(newRi1.getStartKey())).setEndKey(incrementRow(newRi1.getEndKey()))
128      .build();
129    Put p2 = MetaTableAccessor.makePutFromRegionInfo(newRi2, EnvironmentEdgeManager.currentTime());
130    MetaTableAccessor.putsToMetaTable(TEST_UTIL.getConnection(), Arrays.asList(p1, p2));
131    janitor.scan();
132    report = janitor.getLastReport();
133    assertFalse(report.isEmpty());
134    // We added two overlaps so total three.
135    assertEquals(3, report.getOverlaps().size());
136    // Assert hole is still there.
137    assertEquals(1, report.getHoles().size());
138    // Assert other attributes are empty still.
139    assertTrue(report.getEmptyRegionInfo().isEmpty());
140    assertTrue(report.getUnknownServers().isEmpty());
141    // Now make bad server in t1.
142    List<RegionInfo> t1Ris = MetaTableAccessor.getTableRegions(TEST_UTIL.getConnection(), T1);
143    RegionInfo t1Ri1 = t1Ris.get(1);
144    Put pServer = new Put(t1Ri1.getRegionName());
145    pServer.addColumn(HConstants.CATALOG_FAMILY, MetaTableAccessor.getServerColumn(0),
146      Bytes.toBytes("bad.server.example.org:1234"));
147    MetaTableAccessor.putsToMetaTable(TEST_UTIL.getConnection(), Arrays.asList(pServer));
148    janitor.scan();
149    report = janitor.getLastReport();
150    assertFalse(report.isEmpty());
151    assertEquals(1, report.getUnknownServers().size());
152    // Test what happens if we blow away an info:server row, if it is null. Should not kill CJ
153    // and we should log the row that had the problem. HBASE-23192. Just make sure we don't
154    // break if this happens.
155    LOG.info("Make null info:server");
156    Put emptyInfoServerPut = new Put(t1Ri1.getRegionName());
157    emptyInfoServerPut.addColumn(HConstants.CATALOG_FAMILY, MetaTableAccessor.getServerColumn(0),
158      Bytes.toBytes(""));
159    MetaTableAccessor.putsToMetaTable(TEST_UTIL.getConnection(), Arrays.asList(emptyInfoServerPut));
160    janitor.scan();
161    report = janitor.getLastReport();
162    assertEquals(0, report.getUnknownServers().size());
163    // Mke an empty regioninfo in t1.
164    RegionInfo t1Ri2 = t1Ris.get(2);
165    Put pEmptyRI = new Put(t1Ri2.getRegionName());
166    pEmptyRI.addColumn(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER,
167      HConstants.EMPTY_BYTE_ARRAY);
168    MetaTableAccessor.putsToMetaTable(TEST_UTIL.getConnection(), Arrays.asList(pEmptyRI));
169    janitor.scan();
170    report = janitor.getLastReport();
171    assertEquals(1, report.getEmptyRegionInfo().size());
172
173    int holesReported = report.getHoles().size();
174    int overlapsReported = report.getOverlaps().size();
175
176    // Test the case for T4
177    // r1: [aa, bb), r2: [cc, dd), r3: [a, cc)
178    // Make sure only overlaps and no holes are reported.
179    List<RegionInfo> t4Ris = MetaTableAccessor.getTableRegions(TEST_UTIL.getConnection(), T4);
180    // delete the region [bb, cc)
181    MetaTableAccessor.deleteRegionInfo(TEST_UTIL.getConnection(), t4Ris.get(2));
182
183    // add a new region [a, cc)
184    RegionInfo newRiT4 = RegionInfoBuilder.newBuilder(T4).setStartKey("a".getBytes())
185      .setEndKey("cc".getBytes()).build();
186    Put putForT4 =
187      MetaTableAccessor.makePutFromRegionInfo(newRiT4, EnvironmentEdgeManager.currentTime());
188    MetaTableAccessor.putsToMetaTable(TEST_UTIL.getConnection(), Arrays.asList(putForT4));
189
190    janitor.scan();
191    report = janitor.getLastReport();
192    // there is no new hole reported, 2 more overLaps added.
193    assertEquals(holesReported, report.getHoles().size());
194    assertEquals(overlapsReported + 2, report.getOverlaps().size());
195
196    holesReported = report.getHoles().size();
197    overlapsReported = report.getOverlaps().size();
198
199    // Test the case for T5
200    // r0: [, bb), r1: [a, g), r2: [bb, cc), r3: [dd, )
201    // Make sure only overlaps and no holes are reported.
202    List<RegionInfo> t5Ris = MetaTableAccessor.getTableRegions(TEST_UTIL.getConnection(), T5);
203    // delete the region [cc, dd)
204    MetaTableAccessor.deleteRegionInfo(TEST_UTIL.getConnection(), t5Ris.get(2));
205
206    // add a new region [a, g)
207    RegionInfo newRiT5 = RegionInfoBuilder.newBuilder(T5).setStartKey("a".getBytes())
208      .setEndKey("g".getBytes()).build();
209    Put putForT5 =
210      MetaTableAccessor.makePutFromRegionInfo(newRiT5, EnvironmentEdgeManager.currentTime());
211    MetaTableAccessor.putsToMetaTable(TEST_UTIL.getConnection(), Arrays.asList(putForT5));
212
213    janitor.scan();
214    report = janitor.getLastReport();
215    // there is no new hole reported, 3 more overLaps added.
216    // ([a, g), [, bb)), ([a, g), [bb, cc)), ([a, g), [dd, ))
217    assertEquals(holesReported, report.getHoles().size());
218    assertEquals(overlapsReported + 3, report.getOverlaps().size());
219  }
220
221  /**
222   * Take last byte and add one to it.
223   */
224  private static byte[] incrementRow(byte[] row) {
225    if (row.length == 0) {
226      return new byte[] { '0' };
227    }
228    row[row.length - 1] = (byte) (((int) row[row.length - 1]) + 1);
229    return row;
230  }
231
232  @Test
233  public void testHoles() throws IOException {
234    CatalogJanitor janitor = TEST_UTIL.getHBaseCluster().getMaster().getCatalogJanitor();
235
236    CatalogJanitorReport report = janitor.getLastReport();
237    // Assert no problems.
238    assertTrue(report.isEmpty());
239    // Verify start and end region holes
240    verifyCornerHoles(janitor, T1);
241    // Verify start and end region holes
242    verifyCornerHoles(janitor, T2);
243    verifyMiddleHole(janitor);
244    // Verify that MetaFixer is able to fix these holes
245    fixHoles(janitor);
246  }
247
248  private void fixHoles(CatalogJanitor janitor) throws IOException {
249    MetaFixer metaFixer = new MetaFixer(TEST_UTIL.getHBaseCluster().getMaster());
250    janitor.scan();
251    CatalogJanitorReport report = janitor.getLastReport();
252    // Verify total number of holes, 2 in t1 and t2 each and one in t3
253    assertEquals("Number of holes are not matching", 5, report.getHoles().size());
254    metaFixer.fix();
255    janitor.scan();
256    report = janitor.getLastReport();
257    assertEquals("Holes are not fixed", 0, report.getHoles().size());
258  }
259
260  private void verifyMiddleHole(CatalogJanitor janitor) throws IOException {
261    // Verify middle holes
262    RegionInfo firstRegion = getRegionInfo(T3, "".getBytes());
263    RegionInfo secondRegion = getRegionInfo(T3, "bbb".getBytes());
264    RegionInfo thirdRegion = getRegionInfo(T3, "ccc".getBytes());
265    MetaTableAccessor.deleteRegionInfo(TEST_UTIL.getConnection(), secondRegion);
266    LinkedList<Pair<RegionInfo, RegionInfo>> holes = getHoles(janitor, T3);
267    Pair<RegionInfo, RegionInfo> regionInfoRegionInfoPair = holes.getFirst();
268    assertTrue(regionInfoRegionInfoPair.getFirst().getTable().equals(T3));
269    assertTrue(regionInfoRegionInfoPair.getSecond().getTable().equals(T3));
270    assertTrue(
271      regionInfoRegionInfoPair.getFirst().getEncodedName().equals(firstRegion.getEncodedName()));
272    assertTrue(
273      regionInfoRegionInfoPair.getSecond().getEncodedName().equals(thirdRegion.getEncodedName()));
274  }
275
276  private void verifyCornerHoles(CatalogJanitor janitor, TableName tableName) throws IOException {
277    RegionInfo firstRegion = getRegionInfo(tableName, "".getBytes());
278    RegionInfo secondRegion = getRegionInfo(tableName, "bbb".getBytes());
279    MetaTableAccessor.deleteRegionInfo(TEST_UTIL.getConnection(), firstRegion);
280    LinkedList<Pair<RegionInfo, RegionInfo>> holes = getHoles(janitor, tableName);
281
282    assertEquals(1, holes.size());
283    Pair<RegionInfo, RegionInfo> regionInfoRegionInfoPair = holes.get(0);
284    assertTrue(regionInfoRegionInfoPair.getFirst().getTable()
285      .equals(RegionInfoBuilder.UNDEFINED.getTable()));
286    assertTrue(regionInfoRegionInfoPair.getSecond().getTable().equals(tableName));
287    assertTrue(
288      regionInfoRegionInfoPair.getSecond().getEncodedName().equals(secondRegion.getEncodedName()));
289
290    RegionInfo lastRegion = getRegionInfo(tableName, "zzz".getBytes());
291    RegionInfo secondLastRegion = getRegionInfo(tableName, "yyy".getBytes());
292    MetaTableAccessor.deleteRegionInfo(TEST_UTIL.getConnection(), lastRegion);
293    holes = getHoles(janitor, tableName);
294    assertEquals(2, holes.size());
295    regionInfoRegionInfoPair = holes.get(1);
296    assertTrue(regionInfoRegionInfoPair.getFirst().getEncodedName()
297      .equals(secondLastRegion.getEncodedName()));
298    assertTrue(regionInfoRegionInfoPair.getSecond().getTable()
299      .equals(RegionInfoBuilder.UNDEFINED.getTable()));
300  }
301
302  // Get Holes filter by table
303  private LinkedList<Pair<RegionInfo, RegionInfo>> getHoles(CatalogJanitor janitor,
304    TableName tableName) throws IOException {
305    janitor.scan();
306    CatalogJanitorReport lastReport = janitor.getLastReport();
307    assertFalse(lastReport.isEmpty());
308    LinkedList<Pair<RegionInfo, RegionInfo>> holes = new LinkedList<>();
309    for (Pair<RegionInfo, RegionInfo> hole : lastReport.getHoles()) {
310      if (
311        hole.getFirst().getTable().equals(tableName)
312          || hole.getSecond().getTable().equals(tableName)
313      ) {
314        holes.add(hole);
315      }
316    }
317    return holes;
318  }
319
320  private RegionInfo getRegionInfo(TableName tableName, byte[] row) throws IOException {
321    RegionInfo regionInfo =
322      TEST_UTIL.getConnection().getRegionLocator(tableName).getRegionLocation(row).getRegion();
323    assertNotNull(regionInfo);
324    return regionInfo;
325  }
326}