blob: 6ed5087ccbdbb19b4321158ceb76cda9f7aaf31a [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.hadoop.hbase.master.http;
import static org.apache.hadoop.hbase.client.RegionInfoBuilder.FIRST_META_REGIONINFO;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.ConnectionRule;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.MiniClusterRule;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.StartTestingClusterOption;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.AsyncAdmin;
import org.apache.hadoop.hbase.client.AsyncConnection;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
import org.apache.hadoop.hbase.client.Durability;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
import org.apache.hadoop.hbase.master.HMaster;
import org.apache.hadoop.hbase.master.hbck.HbckChore;
import org.apache.hadoop.hbase.master.hbck.HbckReport;
import org.apache.hadoop.hbase.master.http.hbck.resource.HbckMetricsResource;
import org.apache.hadoop.hbase.master.janitor.CatalogJanitor;
import org.apache.hadoop.hbase.master.janitor.CatalogJanitorReport;
import org.apache.hadoop.hbase.testclassification.LargeTests;
import org.apache.hadoop.hbase.testclassification.MasterTests;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.ExternalResource;
import org.junit.rules.RuleChain;
import org.mockito.Mockito;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hbase.thirdparty.javax.ws.rs.NotAcceptableException;
import org.apache.hbase.thirdparty.javax.ws.rs.client.Client;
import org.apache.hbase.thirdparty.javax.ws.rs.client.ClientBuilder;
import org.apache.hbase.thirdparty.javax.ws.rs.client.WebTarget;
import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType;
/**
* Tests for the {@link HbckMetricsResource}.
*/
@Category({ MasterTests.class, LargeTests.class })
public class TestHbckMetricsResource {
private static final Logger LOG = LoggerFactory.getLogger(TestHbckMetricsResource.class);
// Test data for Mock HBCK Report
private static final long reportStartTime = 123456789000L;
private static final long reportEndTime = 234567890000L;
private static final String regionId1 = "regionId1";
private static final String regionId2 = "regionId2";
private static final String localhost1 = "localhost1";
private static final String localhost2 = "localhost2";
private static final String port = "16010";
private static final String hostStartCode = "123456789";
private static final String path1 = "hdfs://path1";
private static final String path2 = "hdfs://path2";
private static final String metaRegionID = FIRST_META_REGIONINFO.getEncodedName();
private static final String metaTableName = FIRST_META_REGIONINFO.getTable().getNameAsString();
// Various Keys in HBCK JSON Response.
private static final String quoteColon = "\":";
private static final String quote = "\"";
private static final String regionId = quote + "region_id" + quoteColon;
private static final String regionHdfsPath = quote + "region_hdfs_path" + quoteColon;
private static final String rsName = quote + "rs_name" + quoteColon;
private static final String hostName = quote + "host_name" + quoteColon;
private static final String hostPort = quote + "host_port" + quoteColon;
private static final String startCode = quote + "start_code" + quoteColon;
private static final String serverNameInMeta = quote + "server_name_in_meta" + quoteColon;
private static final String listOfServers = quote + "list_of_servers" + quoteColon;
private static final String region1Info = quote + "region1_info" + quoteColon;
private static final String region2Info = quote + "region2_info" + quoteColon;
private static final String regionInfo = quote + "region_info" + quoteColon;
private static final String serverName = quote + "server_name" + quoteColon;
private static final String tableName = quote + "table_name" + quoteColon;
private static final String dataStartsWith = "{\"data\":[";
private static final String dataEndsWith = "]}";
private static final String hbckReportStartTime = quote + "hbck_report_start_time" + quoteColon;
private static final String hbckReportEndTime = quote + "hbck_report_end_time" + quoteColon;
private static final String hbckOrphanRegionOnFS =
quote + "hbck_orphan_regions_on_fs" + quoteColon;
private static final String hbckOrphanRegionOnRS =
quote + "hbck_orphan_regions_on_rs" + quoteColon;
private static final String hbckInconsistentRegion =
quote + "hbck_inconsistent_regions" + quoteColon;
private static final String hbckHoles = quote + "hbck_holes" + quoteColon;
private static final String hbckOverlaps = quote + "hbck_overlaps" + quoteColon;
private static final String hbckUnknownServers = quote + "hbck_unknown_servers" + quoteColon;
private static final String hbckEmptyRegionInfo = quote + "hbck_empty_region_info" + quoteColon;
@ClassRule
public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(TestHbckMetricsResource.class);
private static final MiniClusterRule miniClusterRule = MiniClusterRule.newBuilder()
.setMiniClusterOption(
StartTestingClusterOption.builder().numZkServers(3).numMasters(3).numDataNodes(3).build())
.setConfiguration(() -> {
// enable Master InfoServer and random port selection
final Configuration conf = HBaseConfiguration.create();
conf.setInt(HConstants.MASTER_INFO_PORT, 0);
conf.set("hbase.http.jersey.tracing.type", "ON_DEMAND");
return conf;
}).build();
private static final ConnectionRule connectionRule =
ConnectionRule.createAsyncConnectionRule(miniClusterRule::createAsyncConnection);
private static final ClassSetup classRule = new ClassSetup(connectionRule::getAsyncConnection);
private static final class ClassSetup extends ExternalResource {
private final Supplier<AsyncConnection> connectionSupplier;
private final TableName tableName;
private AsyncAdmin admin;
private WebTarget target;
public ClassSetup(final Supplier<AsyncConnection> connectionSupplier) {
this.connectionSupplier = connectionSupplier;
tableName = TableName.valueOf(TestHbckMetricsResource.class.getSimpleName());
}
public WebTarget getTarget() {
return target;
}
@Override
protected void before() throws Throwable {
final AsyncConnection conn = connectionSupplier.get();
admin = conn.getAdmin();
final TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(tableName)
.setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("c")).build())
.setDurability(Durability.SKIP_WAL).build();
admin.createTable(tableDescriptor).get();
HMaster master = miniClusterRule.getTestingUtility().getMiniHBaseCluster().getMaster();
HbckChore hbckChore = mock(HbckChore.class);
HbckReport hbckReport = mock(HbckReport.class);
CatalogJanitor catalogJanitorChore = mock(CatalogJanitor.class);
CatalogJanitorReport catalogJanitorReport = mock(CatalogJanitorReport.class);
master.setHbckChoreForTesting(hbckChore);
master.setCatalogJanitorChoreForTesting(catalogJanitorChore);
// Test data for Mock HBCK Report
ServerName server1 =
ServerName.valueOf(localhost1, Integer.parseInt(port), Integer.parseInt(hostStartCode));
ServerName server2 =
ServerName.valueOf(localhost2, Integer.parseInt(port), Integer.parseInt(hostStartCode));
Path hdfsPath1 = new Path(path1);
Path hdfsPath2 = new Path(path2);
// Orphan on RS Test data
Map<String, ServerName> mapOfOrphanRegionsOnRS = new HashMap<>();
mapOfOrphanRegionsOnRS.put(regionId1, server1);
mapOfOrphanRegionsOnRS.put(regionId2, server2);
// Orphan Region on FS Test Data
Map<String, Path> mapOfOrphanRegionOnFS = new HashMap<>();
mapOfOrphanRegionOnFS.put(regionId1, hdfsPath1);
mapOfOrphanRegionOnFS.put(regionId2, hdfsPath2);
// Inconsistent Regions Test Data
Map<String, Pair<ServerName, List<ServerName>>> mapOfInconsistentRegions = new HashMap<>();
mapOfInconsistentRegions.put(regionId1, new Pair<>(server1, Arrays.asList(server1, server2)));
mapOfInconsistentRegions.put(regionId2, new Pair<>(server2, Arrays.asList(server1, server2)));
// Region Overlap and Region Holes Test Data
List<Pair<RegionInfo, RegionInfo>> listOfRegion = new ArrayList<>();
listOfRegion.add(new Pair<>(FIRST_META_REGIONINFO, FIRST_META_REGIONINFO));
listOfRegion.add(new Pair<>(FIRST_META_REGIONINFO, FIRST_META_REGIONINFO));
// Unknown RegionServer Test Data
List<Pair<RegionInfo, ServerName>> listOfUnknownServers = new ArrayList<>();
listOfUnknownServers.add(new Pair<>(FIRST_META_REGIONINFO, server1));
listOfUnknownServers.add(new Pair<>(FIRST_META_REGIONINFO, server2));
// Empty Region Info Test Data
List<byte[]> listOfEmptyRegionInfo = new ArrayList<>();
listOfEmptyRegionInfo.add(regionId1.getBytes());
listOfEmptyRegionInfo.add(regionId2.getBytes());
// Mock HBCK Report and CatalogJanitor Report
when(hbckReport.getCheckingStartTimestamp())
.thenReturn(Instant.ofEpochMilli(reportStartTime));
when(hbckReport.getCheckingEndTimestamp()).thenReturn(Instant.ofEpochSecond(reportEndTime));
when(hbckReport.getOrphanRegionsOnFS()).thenReturn(mapOfOrphanRegionOnFS);
when(hbckReport.getOrphanRegionsOnRS()).thenReturn(mapOfOrphanRegionsOnRS);
when(hbckReport.getInconsistentRegions()).thenReturn(mapOfInconsistentRegions);
when(catalogJanitorReport.getHoles()).thenReturn(listOfRegion);
when(catalogJanitorReport.getOverlaps()).thenReturn(listOfRegion);
when(catalogJanitorReport.getUnknownServers()).thenReturn(listOfUnknownServers);
when(catalogJanitorReport.getEmptyRegionInfo()).thenReturn(listOfEmptyRegionInfo);
Mockito.doReturn(hbckReport).when(hbckChore).getLastReport();
Mockito.doReturn(catalogJanitorReport).when(catalogJanitorChore).getLastReport();
final String baseUrl =
admin.getMaster().thenApply(ServerName::getHostname).thenCombine(admin.getMasterInfoPort(),
(hostName, infoPort) -> "http://" + hostName + ":" + infoPort).get();
final Client client = ClientBuilder.newClient();
target = client.target(baseUrl).path("hbck/hbck-metrics");
}
@Override
protected void after() {
final TableName tableName = TableName.valueOf("test");
try {
admin.tableExists(tableName).thenCompose(val -> {
if (val) {
return admin.disableTable(tableName)
.thenCompose(ignored -> admin.deleteTable(tableName));
} else {
return CompletableFuture.completedFuture(null);
}
}).get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@ClassRule
public static RuleChain ruleChain =
RuleChain.outerRule(miniClusterRule).around(connectionRule).around(classRule);
@Test
public void testGetRoot() {
final String response = classRule.getTarget().request(MediaType.APPLICATION_JSON_TYPE)
.header("X-Jersey-Tracing-Accept", true).get(String.class);
LOG.info("HBCK JSON Response : " + response);
assertThat(response,
allOf(containsString(hbckReportStartTime), containsString(hbckReportEndTime),
containsString(hbckOrphanRegionOnFS), containsString(hbckOrphanRegionOnRS),
containsString(hbckInconsistentRegion), containsString(hbckHoles),
containsString(hbckOverlaps), containsString(hbckUnknownServers),
containsString(hbckEmptyRegionInfo), containsString(Objects.toString(reportStartTime)),
containsString(Objects.toString(reportEndTime))));
}
@Test
public void testGetRootHtml() {
assertThrows(NotAcceptableException.class, () -> classRule.getTarget()
.request(MediaType.TEXT_HTML_TYPE).header("X-Jersey-Tracing-Accept", true).get(String.class));
}
@Test
public void testGetOrphanRegionOnFS() {
final String response =
classRule.getTarget().path("orphan-regions-on-fs").request(MediaType.APPLICATION_JSON_TYPE)
.header("X-Jersey-Tracing-Accept", true).get(String.class);
LOG.info("HBCK Response for resource orphan-regions-on-fs : " + response);
assertThat(response,
allOf(startsWith(dataStartsWith), endsWith(dataEndsWith), containsString(regionId),
containsString(regionHdfsPath), containsString(regionId1), containsString(regionId2),
containsString(path1), containsString(path2)));
}
@Test
public void testGetOrphanRegionOnFSHtml() {
assertThrows(NotAcceptableException.class,
() -> classRule.getTarget().path("orphan-regions-on-fs").request(MediaType.TEXT_HTML_TYPE)
.header("X-Jersey-Tracing-Accept", true).get(String.class));
}
@Test
public void testGetOrphanRegionOnRS() {
final String response =
classRule.getTarget().path("orphan-regions-on-rs").request(MediaType.APPLICATION_JSON_TYPE)
.header("X-Jersey-Tracing-Accept", true).get(String.class);
LOG.info("HBCK Response for resource orphan-regions-on-rs : " + response);
assertThat(response,
allOf(startsWith(dataStartsWith), endsWith(dataEndsWith), containsString(regionId),
containsString(rsName), containsString(hostName), containsString(hostPort),
containsString(startCode), containsString(regionId1), containsString(regionId2),
containsString(localhost1), containsString(localhost2), containsString(port),
containsString(hostStartCode)));
}
@Test
public void testGetOrphanRegionOnRSHtml() {
assertThrows(NotAcceptableException.class,
() -> classRule.getTarget().path("orphan-regions-on-rs").request(MediaType.TEXT_HTML_TYPE)
.header("X-Jersey-Tracing-Accept", true).get(String.class));
}
@Test
public void testGetInconsistentRegions() {
final String response =
classRule.getTarget().path("inconsistent-regions").request(MediaType.APPLICATION_JSON_TYPE)
.header("X-Jersey-Tracing-Accept", true).get(String.class);
LOG.info("HBCK Response for resource inconsistent-regions : " + response);
assertThat(response,
allOf(startsWith(dataStartsWith), endsWith(dataEndsWith), containsString(hostName),
containsString(hostPort), containsString(startCode), containsString(listOfServers),
containsString(regionId1), containsString(regionId2), containsString(regionId),
containsString(serverNameInMeta), containsString(localhost1), containsString(localhost2),
containsString(port), containsString(hostStartCode)));
}
@Test
public void testGetInconsistentRegionsHtml() {
assertThrows(NotAcceptableException.class,
() -> classRule.getTarget().path("inconsistent-regions").request(MediaType.TEXT_HTML_TYPE)
.header("X-Jersey-Tracing-Accept", true).get(String.class));
}
@Test
public void testGetRegionHoles() {
final String response =
classRule.getTarget().path("region-holes").request(MediaType.APPLICATION_JSON_TYPE)
.header("X-Jersey-Tracing-Accept", true).get(String.class);
LOG.info("HBCK Response for resource region-holes : " + response);
assertThat(response,
allOf(startsWith(dataStartsWith), endsWith(dataEndsWith), containsString(region1Info),
containsString(region2Info), containsString(regionId), containsString(tableName),
containsString(metaRegionID), containsString(metaTableName)));
}
@Test
public void testGetRegionHolesHtml() {
assertThrows(NotAcceptableException.class, () -> classRule.getTarget().path("region-holes")
.request(MediaType.TEXT_HTML_TYPE).header("X-Jersey-Tracing-Accept", true).get(String.class));
}
@Test
public void testGetRegionOverlaps() {
final String response =
classRule.getTarget().path("region-overlaps").request(MediaType.APPLICATION_JSON_TYPE)
.header("X-Jersey-Tracing-Accept", true).get(String.class);
LOG.info("HBCK Response for resource region-overlaps : " + response);
assertThat(response,
allOf(startsWith(dataStartsWith), endsWith(dataEndsWith), containsString(regionId),
containsString(tableName), containsString(region2Info), containsString(region2Info),
containsString(metaRegionID), containsString(metaTableName)));
}
@Test
public void testGetRegionOverlapsHtml() {
assertThrows(NotAcceptableException.class, () -> classRule.getTarget().path("region-overlaps")
.request(MediaType.TEXT_HTML_TYPE).header("X-Jersey-Tracing-Accept", true).get(String.class));
}
@Test
public void testGetUnkownServers() {
final String response =
classRule.getTarget().path("unknown-servers").request(MediaType.APPLICATION_JSON_TYPE)
.header("X-Jersey-Tracing-Accept", true).get(String.class);
LOG.info("HBCK Response for resource unknown-servers : " + response);
assertThat(response,
allOf(startsWith(dataStartsWith), endsWith(dataEndsWith), containsString(regionInfo),
containsString(regionId), containsString(tableName), containsString(serverName),
containsString(serverName), containsString(port), containsString(startCode),
containsString(metaRegionID), containsString(metaTableName), containsString(localhost1),
containsString(localhost2), containsString(port), containsString(startCode)));
}
@Test
public void testGetUnkownServersHtml() {
assertThrows(NotAcceptableException.class, () -> classRule.getTarget().path("unknown-servers")
.request(MediaType.TEXT_HTML_TYPE).header("X-Jersey-Tracing-Accept", true).get(String.class));
}
@Test
public void testGetEmptyRegionInfo() {
final String response =
classRule.getTarget().path("empty-regioninfo").request(MediaType.APPLICATION_JSON_TYPE)
.header("X-Jersey-Tracing-Accept", true).get(String.class);
LOG.info("HBCK Response for resource empty-regioninfo : " + response);
assertThat(response, allOf(startsWith(dataStartsWith), endsWith(dataEndsWith),
containsString(regionInfo), containsString(regionId1), containsString(regionId2)));
}
@Test
public void testGetEmptyRegionInfoHtml() {
assertThrows(NotAcceptableException.class, () -> classRule.getTarget().path("empty-regioninfo")
.request(MediaType.TEXT_HTML_TYPE).header("X-Jersey-Tracing-Accept", true).get(String.class));
}
}