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