blob: 7b9bf2a60e595159d68b69d5adca030341f32ffd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hbase;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.MetaTableAccessor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.ClusterConnection;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.Hbck;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.client.TableState;
import org.apache.hadoop.hbase.master.RegionState;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Threads;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Tests commands. For command-line parsing, see adjacent test.
* @see TestHBCKCommandLineParsing
*/
public class TestHBCK2 {
private static final Logger LOG = LoggerFactory.getLogger(TestHBCK2.class);
private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
private static final TableName TABLE_NAME = TableName.valueOf(TestHBCK2.class.getSimpleName());
private static final TableName REGION_STATES_TABLE_NAME = TableName.
valueOf(TestHBCK2.class.getSimpleName() + "-REGIONS_STATES");
@Rule
public TestName testName = new TestName();
/**
* A 'connected' hbck2 instance.
*/
private HBCK2 hbck2;
@BeforeClass
public static void beforeClass() throws Exception {
TEST_UTIL.startMiniCluster(3);
TEST_UTIL.createMultiRegionTable(TABLE_NAME, Bytes.toBytes("family1"), 5);
}
@AfterClass
public static void afterClass() throws Exception {
TEST_UTIL.shutdownMiniCluster();
}
@Before
public void before() {
this.hbck2 = new HBCK2(TEST_UTIL.getConfiguration());
}
@Test (expected = UnsupportedOperationException.class)
public void testVersions() throws IOException {
try (ClusterConnection connection = this.hbck2.connect()) {
this.hbck2.checkHBCKSupport(connection, "test", "10.0.0");
}
}
@Test
public void testSetTableStateInMeta() throws IOException {
try (ClusterConnection connection = this.hbck2.connect(); Hbck hbck = connection.getHbck()) {
TableState state = this.hbck2.setTableState(hbck, TABLE_NAME, TableState.State.DISABLED);
assertTrue("Found=" + state.getState(), state.isEnabled());
// Restore the state.
state = this.hbck2.setTableState(hbck, TABLE_NAME, state.getState());
assertTrue("Found=" + state.getState(), state.isDisabled());
}
}
@Test
public void testAssigns() throws IOException {
try (Admin admin = TEST_UTIL.getConnection().getAdmin()) {
List<RegionInfo> regions = admin.getRegions(TABLE_NAME);
for (RegionInfo ri: regions) {
RegionState rs = TEST_UTIL.getHBaseCluster().getMaster().getAssignmentManager().
getRegionStates().getRegionState(ri.getEncodedName());
LOG.info("RS: {}", rs.toString());
}
List<String> regionStrs =
regions.stream().map(RegionInfo::getEncodedName).collect(Collectors.toList());
String [] regionStrsArray = regionStrs.toArray(new String[] {});
try (ClusterConnection connection = this.hbck2.connect(); Hbck hbck = connection.getHbck()) {
List<Long> pids = this.hbck2.unassigns(hbck, regionStrsArray);
waitOnPids(pids);
for (RegionInfo ri : regions) {
RegionState rs = TEST_UTIL.getHBaseCluster().getMaster().getAssignmentManager().
getRegionStates().getRegionState(ri.getEncodedName());
LOG.info("RS: {}", rs.toString());
assertTrue(rs.toString(), rs.isClosed());
}
pids = this.hbck2.assigns(hbck, regionStrsArray);
waitOnPids(pids);
for (RegionInfo ri : regions) {
RegionState rs = TEST_UTIL.getHBaseCluster().getMaster().getAssignmentManager().
getRegionStates().getRegionState(ri.getEncodedName());
LOG.info("RS: {}", rs.toString());
assertTrue(rs.toString(), rs.isOpened());
}
// What happens if crappy region list passed?
pids = this.hbck2.assigns(hbck, Arrays.stream(new String[]{"a", "some rubbish name"}).
collect(Collectors.toList()).toArray(new String[]{}));
for (long pid : pids) {
assertEquals(org.apache.hadoop.hbase.procedure2.Procedure.NO_PROC_ID, pid);
}
}
}
}
@Test
public void testSetRegionState() throws IOException {
TEST_UTIL.createTable(REGION_STATES_TABLE_NAME, Bytes.toBytes("family1"));
try (Admin admin = TEST_UTIL.getConnection().getAdmin()) {
List<RegionInfo> regions = admin.getRegions(REGION_STATES_TABLE_NAME);
RegionInfo info = regions.get(0);
assertEquals(RegionState.State.OPEN, getCurrentRegionState(info));
String region = info.getEncodedName();
try (ClusterConnection connection = this.hbck2.connect()) {
this.hbck2.setRegionState(connection, region, RegionState.State.CLOSING);
}
assertEquals(RegionState.State.CLOSING, getCurrentRegionState(info));
} finally {
TEST_UTIL.deleteTable(REGION_STATES_TABLE_NAME);
}
}
@Test
public void testSetRegionStateInvalidRegion() throws IOException {
try (ClusterConnection connection = this.hbck2.connect()) {
assertEquals(HBCK2.EXIT_FAILURE, this.hbck2.setRegionState(connection, "NO_REGION",
RegionState.State.CLOSING));
}
}
@Test (expected = IllegalArgumentException.class)
public void testSetRegionStateInvalidState() throws IOException {
TEST_UTIL.createTable(REGION_STATES_TABLE_NAME, Bytes.toBytes("family1"));
try (Admin admin = TEST_UTIL.getConnection().getAdmin()) {
List<RegionInfo> regions = admin.getRegions(REGION_STATES_TABLE_NAME);
RegionInfo info = regions.get(0);
assertEquals(RegionState.State.OPEN, getCurrentRegionState(info));
String region = info.getEncodedName();
try (ClusterConnection connection = this.hbck2.connect()) {
this.hbck2.setRegionState(connection, region, null);
}
} finally {
TEST_UTIL.deleteTable(REGION_STATES_TABLE_NAME);
}
}
@Test
public void testAddMissingRegionsInMetaAllRegionsMissing() throws Exception {
this.testAddMissingRegionsInMetaForTables(5,5);
}
@Test
public void testAddMissingRegionsInMetaTwoMissingOnly() throws Exception {
this.testAddMissingRegionsInMetaForTables(2,5);
}
@Test
public void testReportMissingRegionsInMetaAllNsTbls() throws Exception {
String[] nullArgs = null;
this.testReportMissingRegionsInMeta(5, 5,
nullArgs);
}
@Test
public void testReportMissingRegionsInMetaSpecificTbl() throws Exception {
this.testReportMissingRegionsInMeta(5, 5,
TABLE_NAME.getNameWithNamespaceInclAsString());
}
@Test
public void testReportMissingRegionsInMetaSpecificTblAndNsTbl() throws Exception {
this.testReportMissingRegionsInMeta(5, 5,
TABLE_NAME.getNameWithNamespaceInclAsString(), "hbase:namespace");
}
@Test
public void testReportMissingRegionsInMetaSpecificTblAndNsTblAlsoMissing() throws Exception {
List<RegionInfo> regions = MetaTableAccessor
.getTableRegions(TEST_UTIL.getConnection(), TableName.valueOf("hbase:namespace"));
HBCKMetaTableAccessor.deleteRegionInfo(TEST_UTIL.getConnection(), regions.get(0));
this.testReportMissingRegionsInMeta(5, 6,
TABLE_NAME.getNameWithNamespaceInclAsString(), "hbase:namespace");
}
@Test
public void testFormatReportMissingRegionsInMetaNoMissing() throws IOException {
final String expectedResult = "Missing Regions for each table:\n"
+ "\tTestHBCK2 -> No missing regions\n\thbase:namespace -> No missing regions\n\t\n";
String result = testFormatMissingRegionsInMetaReport();
assertTrue(result.contains(expectedResult));
}
@Test
public void testFormatReportMissingInMetaOneMissing() throws IOException {
TableName tableName = createTestTable(5);
List<RegionInfo> regions = MetaTableAccessor
.getTableRegions(TEST_UTIL.getConnection(), tableName);
HBCKMetaTableAccessor.deleteRegionInfo(TEST_UTIL.getConnection(), regions.get(0));
String expectedResult = "Missing Regions for each table:\n";
String result = testFormatMissingRegionsInMetaReport();
//validates initial report message
assertTrue(result.contains(expectedResult));
//validates our test table region is reported missing
expectedResult = "\t" + tableName.getNameAsString() + "->\n\t\t"
+ regions.get(0).getEncodedName();
assertTrue(result.contains(expectedResult));
//validates namespace region is not reported missing
expectedResult = "\n\thbase:namespace -> No missing regions\n\t";
assertTrue(result.contains(expectedResult));
}
private String testFormatMissingRegionsInMetaReport()
throws IOException {
HBCK2 hbck = new HBCK2(TEST_UTIL.getConfiguration());
final StringBuilder builder = new StringBuilder();
PrintStream originalOS = System.out;
OutputStream testOS = new OutputStream() {
@Override public void write(int b) {
builder.append((char)b);
}
};
System.setOut(new PrintStream(testOS));
hbck.run(new String[]{"reportMissingRegionsInMeta"});
System.setOut(originalOS);
return builder.toString();
}
private TableName createTestTable(int totalRegions) throws IOException {
TableName tableName = TableName.valueOf(testName.getMethodName());
TEST_UTIL.createMultiRegionTable(tableName, Bytes.toBytes("family1"), totalRegions);
return tableName;
}
private void testAddMissingRegionsInMetaForTables(int missingRegions, int totalRegions)
throws Exception {
TableName tableName = createTestTable(totalRegions);
HBCK2 hbck = new HBCK2(TEST_UTIL.getConfiguration());
List<RegionInfo> regions = MetaTableAccessor
.getTableRegions(TEST_UTIL.getConnection(), tableName);
Connection connection = TEST_UTIL.getConnection();
regions.subList(0, missingRegions).forEach(r -> deleteRegionInfo(connection, r));
int remaining = totalRegions - missingRegions;
assertEquals("Table should have " + remaining + " regions in META.", remaining,
MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), tableName));
assertEquals(missingRegions,hbck.addMissingRegionsInMetaForTables("default:"
+ tableName.getNameAsString()).getFirst().size());
assertEquals("Table regions should had been re-added in META.", totalRegions,
MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), tableName));
//compare the added regions to make sure those are the same
List<RegionInfo> newRegions = MetaTableAccessor
.getTableRegions(TEST_UTIL.getConnection(), tableName);
assertEquals("All re-added regions should be the same", regions, newRegions);
}
private void testReportMissingRegionsInMeta(int missingRegionsInTestTbl,
int expectedTotalMissingRegions, String... namespaceOrTable) throws Exception {
List<RegionInfo> regions = MetaTableAccessor
.getTableRegions(TEST_UTIL.getConnection(), TABLE_NAME);
Connection connection = TEST_UTIL.getConnection();
regions.subList(0, missingRegionsInTestTbl).forEach(r -> deleteRegionInfo(connection, r));
HBCK2 hbck = new HBCK2(TEST_UTIL.getConfiguration());
final Map<TableName,List<Path>> report =
hbck.reportTablesWithMissingRegionsInMeta(namespaceOrTable);
long resultingMissingRegions = report.keySet().stream().mapToLong(nsTbl ->
report.get(nsTbl).size()).sum();
assertEquals(expectedTotalMissingRegions, resultingMissingRegions);
String[] nullArgs = null;
hbck.addMissingRegionsInMetaForTables(nullArgs);
}
@Test (expected = IllegalArgumentException.class)
public void testSetRegionStateInvalidRegionAndInvalidState() throws IOException {
try (ClusterConnection connection = this.hbck2.connect()) {
this.hbck2.setRegionState(connection, "NO_REGION", null);
}
}
private RegionState.State getCurrentRegionState(RegionInfo regionInfo) throws IOException{
Table metaTable = TEST_UTIL.getConnection().getTable(TableName.valueOf("hbase:meta"));
Get get = new Get(regionInfo.getRegionName());
get.addColumn(HConstants.CATALOG_FAMILY, HConstants.STATE_QUALIFIER);
Result result = metaTable.get(get);
byte[] currentStateValue = result.getValue(HConstants.CATALOG_FAMILY,
HConstants.STATE_QUALIFIER);
return currentStateValue != null ?
RegionState.State.valueOf(Bytes.toString(currentStateValue))
: null;
}
private void waitOnPids(List<Long> pids) {
for (Long pid: pids) {
while (!TEST_UTIL.getHBaseCluster().getMaster().getMasterProcedureExecutor().
isFinished(pid)) {
Threads.sleep(100);
}
}
}
private void deleteRegionInfo(Connection connection, RegionInfo region) {
try {
HBCKMetaTableAccessor.deleteRegionInfo(connection, region);
} catch (IOException e) {
fail(e.getMessage());
}
}
}