This is an automated email from the ASF dual-hosted git repository.

apurtell pushed a commit to branch branch-2.6
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/branch-2.6 by this push:
     new b8c797660f2 HBASE-26192 Master UI hbck should provide a JSON formatted 
output option (#5780)
b8c797660f2 is described below

commit b8c797660f2e7a42aeb045271cf85ac1da6e279d
Author: Monani Mihir <monani.mi...@gmail.com>
AuthorDate: Mon Apr 8 14:39:13 2024 -0700

    HBASE-26192 Master UI hbck should provide a JSON formatted output option 
(#5780)
    
    Signed-off-by: Andrew Purtell <apurt...@apache.org>
---
 .../apache/hadoop/hbase/HbckEmptyRegionInfo.java   |  38 ++
 .../hadoop/hbase/HbckInconsistentRegions.java      |  51 +++
 .../apache/hadoop/hbase/HbckOrphanRegionsOnFS.java |  43 +++
 .../apache/hadoop/hbase/HbckOrphanRegionsOnRS.java |  43 +++
 .../apache/hadoop/hbase/HbckOverlapRegions.java    |  44 +++
 .../org/apache/hadoop/hbase/HbckRegionDetails.java |  54 +++
 .../org/apache/hadoop/hbase/HbckRegionHoles.java   |  44 +++
 .../org/apache/hadoop/hbase/HbckServerName.java    |  48 +++
 .../apache/hadoop/hbase/HbckUnknownServers.java    |  44 +++
 .../org/apache/hadoop/hbase/master/HMaster.java    |  24 ++
 .../apache/hadoop/hbase/master/MasterServices.java |   4 +
 .../hbase/master/http/hbck/HbckConfigFactory.java  |  54 +++
 .../hbase/master/http/hbck/model/HbckMetrics.java  |  98 +++++
 .../http/hbck/resource/HbckMetricsResource.java    | 140 +++++++
 .../hbase/master/MockNoopMasterServices.java       |   6 +
 .../hbase/master/http/TestHbckMetricsResource.java | 422 +++++++++++++++++++++
 16 files changed, 1157 insertions(+)

diff --git 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckEmptyRegionInfo.java 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckEmptyRegionInfo.java
new file mode 100644
index 00000000000..5d1ca54bf1b
--- /dev/null
+++ 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckEmptyRegionInfo.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+import org.apache.yetus.audience.InterfaceAudience;
+
+/**
+ * POJO to present Empty Region Info from Catalog Janitor Inconsistencies 
Report via REST API. These
+ * inconsistencies are shown on hbck.jsp page on Active HMaster UI as part of 
Catalog Janitor
+ * inconsistencies.
+ */
+@InterfaceAudience.Public
+public class HbckEmptyRegionInfo {
+  private final String regionInfo;
+
+  public HbckEmptyRegionInfo(String emptyRegionInfo) {
+    this.regionInfo = emptyRegionInfo;
+  }
+
+  public String getRegionInfo() {
+    return regionInfo;
+  }
+}
diff --git 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckInconsistentRegions.java
 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckInconsistentRegions.java
new file mode 100644
index 00000000000..f32f73a73d1
--- /dev/null
+++ 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckInconsistentRegions.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+import java.util.List;
+import org.apache.yetus.audience.InterfaceAudience;
+
+/**
+ * POJO to present HBCK Inconsistent Regions from HBCK Inconsistencies Report 
via REST API. These
+ * inconsistencies are shown on hbck.jsp page on Active HMaster UI as part of 
HBCK inconsistencies.
+ */
+@InterfaceAudience.Public
+public class HbckInconsistentRegions {
+  private final String regionId;
+  private final HbckServerName serverNameInMeta;
+  private final List<HbckServerName> listOfServers;
+
+  public HbckInconsistentRegions(String inconsistentRegionId, HbckServerName 
serverNameInMeta,
+    List<HbckServerName> listOfServerName) {
+    this.regionId = inconsistentRegionId;
+    this.serverNameInMeta = serverNameInMeta;
+    this.listOfServers = listOfServerName;
+  }
+
+  public String getRegionId() {
+    return regionId;
+  }
+
+  public HbckServerName getServerNameInMeta() {
+    return serverNameInMeta;
+  }
+
+  public List<HbckServerName> getListOfServers() {
+    return listOfServers;
+  }
+}
diff --git 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckOrphanRegionsOnFS.java 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckOrphanRegionsOnFS.java
new file mode 100644
index 00000000000..43a045fb293
--- /dev/null
+++ 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckOrphanRegionsOnFS.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+import org.apache.yetus.audience.InterfaceAudience;
+
+/**
+ * POJO to present Orphan Region on FS from HBCK Inconsistencies Report via 
REST API. These
+ * inconsistencies are shown on hbck.jsp page on Active HMaster UI as part of 
HBCK Inconsistencies.
+ */
+@InterfaceAudience.Public
+public class HbckOrphanRegionsOnFS {
+  private final String regionId;
+  private final String regionHdfsPath;
+
+  public HbckOrphanRegionsOnFS(String regionId, String orphanRegionHdfsPath) {
+    this.regionId = regionId;
+    this.regionHdfsPath = orphanRegionHdfsPath;
+  }
+
+  public String getRegionId() {
+    return regionId;
+  }
+
+  public String getRegionHdfsPath() {
+    return regionHdfsPath;
+  }
+}
diff --git 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckOrphanRegionsOnRS.java 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckOrphanRegionsOnRS.java
new file mode 100644
index 00000000000..2d442b7a9e4
--- /dev/null
+++ 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckOrphanRegionsOnRS.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+import org.apache.yetus.audience.InterfaceAudience;
+
+/**
+ * POJO to present Orphan Region on RS from HBCK Inconsistencies Report via 
REST API. These
+ * inconsistencies are shown on hbck.jsp page on Active HMaster UI as part of 
HBCK Inconsistencies.
+ */
+@InterfaceAudience.Public
+public class HbckOrphanRegionsOnRS {
+  private final String regionId;
+  private final HbckServerName rsName;
+
+  public HbckOrphanRegionsOnRS(String orphanRegionId, HbckServerName 
orphanRegionRsName) {
+    this.regionId = orphanRegionId;
+    this.rsName = orphanRegionRsName;
+  }
+
+  public String getRegionId() {
+    return regionId;
+  }
+
+  public HbckServerName getRsName() {
+    return rsName;
+  }
+}
diff --git 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckOverlapRegions.java 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckOverlapRegions.java
new file mode 100644
index 00000000000..4170932bf56
--- /dev/null
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckOverlapRegions.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+import org.apache.yetus.audience.InterfaceAudience;
+
+/**
+ * POJO to present Region Overlap from Catalog Janitor Inconsistencies Report 
via REST API. These
+ * inconsistencies are shown on hbck.jsp page on Active HMaster UI as part of 
Catalog Janitor
+ * inconsistencies.
+ */
+@InterfaceAudience.Public
+public class HbckOverlapRegions {
+  private final HbckRegionDetails region1Info;
+  private final HbckRegionDetails region2Info;
+
+  public HbckOverlapRegions(HbckRegionDetails region1Info, HbckRegionDetails 
region2Info) {
+    this.region1Info = region1Info;
+    this.region2Info = region2Info;
+  }
+
+  public HbckRegionDetails getRegion1Info() {
+    return region1Info;
+  }
+
+  public HbckRegionDetails getRegion2Info() {
+    return region2Info;
+  }
+}
diff --git 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckRegionDetails.java 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckRegionDetails.java
new file mode 100644
index 00000000000..a7924563627
--- /dev/null
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckRegionDetails.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+import org.apache.yetus.audience.InterfaceAudience;
+
+/**
+ * POJO class for HBCK RegionInfo in HBCK Inconsistencies report.
+ */
+@InterfaceAudience.Public
+public class HbckRegionDetails {
+  private final String regionId;
+  private final String tableName;
+  private final String startKey;
+  private final String endKey;
+
+  public HbckRegionDetails(String regionId, String tableName, String startKey, 
String endKey) {
+    this.regionId = regionId;
+    this.tableName = tableName;
+    this.startKey = startKey;
+    this.endKey = endKey;
+  }
+
+  public String getRegionId() {
+    return regionId;
+  }
+
+  public String getTableName() {
+    return tableName;
+  }
+
+  public String getStartKey() {
+    return startKey;
+  }
+
+  public String getEndKey() {
+    return endKey;
+  }
+}
diff --git 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckRegionHoles.java 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckRegionHoles.java
new file mode 100644
index 00000000000..643e014735a
--- /dev/null
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckRegionHoles.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+import org.apache.yetus.audience.InterfaceAudience;
+
+/**
+ * POJO to present Region Holes from Catalog Janitor Inconsistencies Report 
via REST API. These
+ * inconsistencies are shown on hbck.jsp page on Active HMaster UI as part of 
Catalog Janitor
+ * inconsistencies.
+ */
+@InterfaceAudience.Public
+public class HbckRegionHoles {
+  private final HbckRegionDetails region1Info;
+  private final HbckRegionDetails region2Info;
+
+  public HbckRegionHoles(HbckRegionDetails region1Info, HbckRegionDetails 
region2Info) {
+    this.region1Info = region1Info;
+    this.region2Info = region2Info;
+  }
+
+  public HbckRegionDetails getRegion1Info() {
+    return region1Info;
+  }
+
+  public HbckRegionDetails getRegion2Info() {
+    return region2Info;
+  }
+}
diff --git 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckServerName.java 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckServerName.java
new file mode 100644
index 00000000000..2c6b899fb15
--- /dev/null
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckServerName.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import org.apache.yetus.audience.InterfaceAudience;
+
+/**
+ * POJO class for ServerName in HBCK Inconsistencies report.
+ */
+@InterfaceAudience.Public
+public class HbckServerName {
+  private final String hostName;
+  private final int hostPort;
+  private final long startCode;
+
+  public HbckServerName(String hostName, int hostPort, long startCode) {
+    this.hostName = hostName;
+    this.hostPort = hostPort;
+    this.startCode = startCode;
+  }
+
+  public String getHostName() {
+    return hostName;
+  }
+
+  public int getHostPort() {
+    return hostPort;
+  }
+
+  public long getStartCode() {
+    return startCode;
+  }
+}
diff --git 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckUnknownServers.java 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckUnknownServers.java
new file mode 100644
index 00000000000..c070f84e69f
--- /dev/null
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/HbckUnknownServers.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+import org.apache.yetus.audience.InterfaceAudience;
+
+/**
+ * POJO to present Unknown Regions from Catalog Janitor Inconsistencies Report 
via REST API. These
+ * inconsistencies are shown on hbck.jsp page on Active HMaster UI as part of 
Catalog Janitor
+ * inconsistencies.
+ */
+@InterfaceAudience.Public
+public class HbckUnknownServers {
+  private final HbckRegionDetails regionInfo;
+  private final HbckServerName serverName;
+
+  public HbckUnknownServers(HbckRegionDetails regionInfo, HbckServerName 
unknownServerName) {
+    this.regionInfo = regionInfo;
+    this.serverName = unknownServerName;
+  }
+
+  public HbckRegionDetails getRegionInfo() {
+    return regionInfo;
+  }
+
+  public HbckServerName getServerName() {
+    return serverName;
+  }
+}
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
index 61e3048d9f0..5c97981b405 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
@@ -140,6 +140,7 @@ import 
org.apache.hadoop.hbase.master.http.MasterDumpServlet;
 import org.apache.hadoop.hbase.master.http.MasterRedirectServlet;
 import org.apache.hadoop.hbase.master.http.MasterStatusServlet;
 import org.apache.hadoop.hbase.master.http.api_v1.ResourceConfigFactory;
+import org.apache.hadoop.hbase.master.http.hbck.HbckConfigFactory;
 import org.apache.hadoop.hbase.master.janitor.CatalogJanitor;
 import org.apache.hadoop.hbase.master.locking.LockManager;
 import org.apache.hadoop.hbase.master.migrate.RollingUpgradeChore;
@@ -740,6 +741,7 @@ public class HMaster extends HRegionServer implements 
MasterServices {
   protected void configureInfoServer() {
     infoServer.addUnprivilegedServlet("master-status", "/master-status", 
MasterStatusServlet.class);
     infoServer.addUnprivilegedServlet("api_v1", "/api/v1/*", 
buildApiV1Servlet());
+    infoServer.addUnprivilegedServlet("hbck", "/hbck/*", buildHbckServlet());
 
     infoServer.setAttribute(MASTER, this);
     if (LoadBalancer.isTablesOnMaster(conf)) {
@@ -752,6 +754,11 @@ public class HMaster extends HRegionServer implements 
MasterServices {
     return new ServletHolder(new ServletContainer(config));
   }
 
+  private ServletHolder buildHbckServlet() {
+    final ResourceConfig config = HbckConfigFactory.createResourceConfig(conf, 
this);
+    return new ServletHolder(new ServletContainer(config));
+  }
+
   @Override
   protected Class<? extends HttpServlet> getDumpServlet() {
     return MasterDumpServlet.class;
@@ -1324,6 +1331,22 @@ public class HMaster extends HRegionServer implements 
MasterServices {
     status.markComplete("Progress after master initialized complete");
   }
 
+  /**
+   * Used for testing only to set Mock objects.
+   * @param hbckChore hbckChore
+   */
+  public void setHbckChoreForTesting(HbckChore hbckChore) {
+    this.hbckChore = hbckChore;
+  }
+
+  /**
+   * Used for testing only to set Mock objects.
+   * @param catalogJanitorChore catalogJanitorChore
+   */
+  public void setCatalogJanitorChoreForTesting(CatalogJanitor 
catalogJanitorChore) {
+    this.catalogJanitorChore = catalogJanitorChore;
+  }
+
   private void createMissingCFsInMetaDuringUpgrade(TableDescriptor 
metaDescriptor)
     throws IOException {
     TableDescriptor newMetaDesc = 
TableDescriptorBuilder.newBuilder(metaDescriptor)
@@ -4179,6 +4202,7 @@ public class HMaster extends HRegionServer implements 
MasterServices {
     return super.getWalGroupsReplicationStatus();
   }
 
+  @Override
   public HbckChore getHbckChore() {
     return this.hbckChore;
   }
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java
index a288bd351c3..ed87a847bff 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java
@@ -34,6 +34,7 @@ import org.apache.hadoop.hbase.client.TableDescriptor;
 import org.apache.hadoop.hbase.executor.ExecutorService;
 import org.apache.hadoop.hbase.favored.FavoredNodesManager;
 import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
+import org.apache.hadoop.hbase.master.hbck.HbckChore;
 import org.apache.hadoop.hbase.master.janitor.CatalogJanitor;
 import org.apache.hadoop.hbase.master.locking.LockManager;
 import org.apache.hadoop.hbase.master.normalizer.RegionNormalizerManager;
@@ -100,6 +101,9 @@ public interface MasterServices extends Server {
   /** Returns Master's instance of {@link CatalogJanitor} */
   CatalogJanitor getCatalogJanitor();
 
+  /** Returns Master's instance of {@link HbckChore} */
+  HbckChore getHbckChore();
+
   /** Returns Master's instance of {@link ProcedureExecutor} */
   ProcedureExecutor<MasterProcedureEnv> getMasterProcedureExecutor();
 
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/hbck/HbckConfigFactory.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/hbck/HbckConfigFactory.java
new file mode 100644
index 00000000000..32dfd4a23b9
--- /dev/null
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/hbck/HbckConfigFactory.java
@@ -0,0 +1,54 @@
+/*
+ * 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.hbck;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.http.jersey.ResponseEntityMapper;
+import org.apache.hadoop.hbase.master.HMaster;
+import org.apache.hadoop.hbase.master.http.gson.GsonSerializationFeature;
+import org.apache.hadoop.hbase.master.http.jersey.MasterFeature;
+import org.apache.yetus.audience.InterfaceAudience;
+
+import org.apache.hbase.thirdparty.org.glassfish.jersey.server.ResourceConfig;
+import 
org.apache.hbase.thirdparty.org.glassfish.jersey.server.ServerProperties;
+import org.apache.hbase.thirdparty.org.glassfish.jersey.server.TracingConfig;
+
+@InterfaceAudience.Private
+public final class HbckConfigFactory {
+  private HbckConfigFactory() {
+  }
+
+  public static ResourceConfig createResourceConfig(Configuration conf, 
HMaster master) {
+    return new ResourceConfig().setApplicationName("hbck")
+      .packages(HbckConfigFactory.class.getPackage().getName())
+      // TODO: anything registered here that does not have necessary bindings 
won't inject properly
+      // at annotation sites and will result in a WARN logged by 
o.a.h.t.o.g.j.i.inject.Providers.
+      // These warnings should be treated by the service as fatal errors, but 
I have not found a
+      // callback API for registering a failed binding handler.
+      
.register(ResponseEntityMapper.class).register(GsonSerializationFeature.class)
+      .register(new MasterFeature(master))
+
+      // devs: enable TRACING to see how jersey is dispatching to resources.
+      // in hbase-site.xml, set 'hbase.http.jersey.tracing.type=ON_DEMAND` and
+      // to curl, add `-H X-Jersey-Tracing-Accept:true`
+      .property(ServerProperties.TRACING,
+        conf.get("hbase.http.jersey.tracing.type", TracingConfig.OFF.name()))
+      .property(ServerProperties.TRACING_THRESHOLD,
+        conf.get("hbase.http.jersey.tracing.threshold", "TRACE"));
+  }
+}
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/hbck/model/HbckMetrics.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/hbck/model/HbckMetrics.java
new file mode 100644
index 00000000000..ba4cfecdcf4
--- /dev/null
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/hbck/model/HbckMetrics.java
@@ -0,0 +1,98 @@
+/*
+ * 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.hbck.model;
+
+import java.util.List;
+import org.apache.hadoop.hbase.HbckEmptyRegionInfo;
+import org.apache.hadoop.hbase.HbckInconsistentRegions;
+import org.apache.hadoop.hbase.HbckOrphanRegionsOnFS;
+import org.apache.hadoop.hbase.HbckOrphanRegionsOnRS;
+import org.apache.hadoop.hbase.HbckOverlapRegions;
+import org.apache.hadoop.hbase.HbckRegionHoles;
+import org.apache.hadoop.hbase.HbckUnknownServers;
+import org.apache.yetus.audience.InterfaceAudience;
+
+/**
+ * This class exposes hbck.jsp report as JSON Output via /hbck/hbck-metrics 
API.
+ */
+@InterfaceAudience.Private
+public class HbckMetrics {
+
+  private final long hbckReportStartTime;
+  private final long hbckReportEndTime;
+  private final List<HbckOrphanRegionsOnFS> hbckOrphanRegionsOnFs;
+  private final List<HbckOrphanRegionsOnRS> hbckOrphanRegionsOnRs;
+  private final List<HbckInconsistentRegions> hbckInconsistentRegions;
+  private final List<HbckRegionHoles> hbckHoles;
+  private final List<HbckOverlapRegions> hbckOverlaps;
+  private final List<HbckUnknownServers> hbckUnknownServers;
+  private final List<HbckEmptyRegionInfo> hbckEmptyRegionInfo;
+
+  public HbckMetrics(long hbckReportStartTime, long hbckReportEndTime,
+    List<HbckOrphanRegionsOnFS> hbckOrphanRegionsOnFs,
+    List<HbckOrphanRegionsOnRS> hbckOrphanRegionsOnRs,
+    List<HbckInconsistentRegions> hbckInconsistentRegions, 
List<HbckRegionHoles> hbckHoles,
+    List<HbckOverlapRegions> hbckOverlaps, List<HbckUnknownServers> 
hbckUnknownServers,
+    List<HbckEmptyRegionInfo> hbckEmptyRegionInfo) {
+    this.hbckReportStartTime = hbckReportStartTime;
+    this.hbckReportEndTime = hbckReportEndTime;
+    this.hbckOrphanRegionsOnFs = hbckOrphanRegionsOnFs;
+    this.hbckOrphanRegionsOnRs = hbckOrphanRegionsOnRs;
+    this.hbckInconsistentRegions = hbckInconsistentRegions;
+    this.hbckHoles = hbckHoles;
+    this.hbckOverlaps = hbckOverlaps;
+    this.hbckUnknownServers = hbckUnknownServers;
+    this.hbckEmptyRegionInfo = hbckEmptyRegionInfo;
+  }
+
+  public long gethbckReportStartTime() {
+    return hbckReportStartTime;
+  }
+
+  public long gethbckReportEndTime() {
+    return hbckReportEndTime;
+  }
+
+  public List<HbckOrphanRegionsOnFS> gethbckOrphanRegionsOnFs() {
+    return hbckOrphanRegionsOnFs;
+  }
+
+  public List<HbckOrphanRegionsOnRS> gethbckOrphanRegionsOnRs() {
+    return hbckOrphanRegionsOnRs;
+  }
+
+  public List<HbckInconsistentRegions> gethbckInconsistentRegions() {
+    return hbckInconsistentRegions;
+  }
+
+  public List<HbckRegionHoles> gethbckHoles() {
+    return hbckHoles;
+  }
+
+  public List<HbckOverlapRegions> gethbckOverlaps() {
+    return hbckOverlaps;
+  }
+
+  public List<HbckUnknownServers> gethbckUnknownServers() {
+    return hbckUnknownServers;
+  }
+
+  public List<HbckEmptyRegionInfo> gethbckEmptyRegionInfo() {
+    return hbckEmptyRegionInfo;
+  }
+}
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/hbck/resource/HbckMetricsResource.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/hbck/resource/HbckMetricsResource.java
new file mode 100644
index 00000000000..96924aa126d
--- /dev/null
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/hbck/resource/HbckMetricsResource.java
@@ -0,0 +1,140 @@
+/*
+ * 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.hbck.resource;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.inject.Inject;
+import org.apache.hadoop.hbase.HbckEmptyRegionInfo;
+import org.apache.hadoop.hbase.HbckInconsistentRegions;
+import org.apache.hadoop.hbase.HbckOrphanRegionsOnFS;
+import org.apache.hadoop.hbase.HbckOrphanRegionsOnRS;
+import org.apache.hadoop.hbase.HbckOverlapRegions;
+import org.apache.hadoop.hbase.HbckRegionDetails;
+import org.apache.hadoop.hbase.HbckRegionHoles;
+import org.apache.hadoop.hbase.HbckServerName;
+import org.apache.hadoop.hbase.HbckUnknownServers;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.client.RegionInfo;
+import org.apache.hadoop.hbase.master.MasterServices;
+import org.apache.hadoop.hbase.master.hbck.HbckReport;
+import org.apache.hadoop.hbase.master.http.hbck.model.HbckMetrics;
+import org.apache.hadoop.hbase.master.janitor.CatalogJanitorReport;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.yetus.audience.InterfaceAudience;
+
+import org.apache.hbase.thirdparty.javax.ws.rs.GET;
+import org.apache.hbase.thirdparty.javax.ws.rs.Path;
+import org.apache.hbase.thirdparty.javax.ws.rs.Produces;
+import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType;
+
+/**
+ * The root object exposing hbck.jsp page as JSON Output.
+ */
+@Path("hbck-metrics")
+@Produces({ MediaType.APPLICATION_JSON })
+@InterfaceAudience.Private
+public class HbckMetricsResource {
+  private final HbckReport hbckReport;
+  private final CatalogJanitorReport catalogJanitorReport;
+
+  @Inject
+  public HbckMetricsResource(MasterServices master) {
+    this.hbckReport = master.getHbckChore().getLastReport();
+    this.catalogJanitorReport = master.getCatalogJanitor().getLastReport();
+  }
+
+  @GET
+  public HbckMetrics getBaseHbckMetrics() {
+    return new 
HbckMetrics(hbckReport.getCheckingStartTimestamp().toEpochMilli(),
+      hbckReport.getCheckingEndTimestamp().toEpochMilli(), 
getOrphanRegionsOnFS(),
+      getOrphanRegionsOnRS(), getInconsistentRegions(), getRegionChainHoles(),
+      getRegionChainOverlap(), getUnknownServers(), getEmptyRegionInfo());
+  }
+
+  @GET
+  @Path("/orphan-regions-on-fs")
+  public List<HbckOrphanRegionsOnFS> getOrphanRegionsOnFS() {
+    return hbckReport.getOrphanRegionsOnFS().entrySet().stream()
+      .map(obj1 -> new HbckOrphanRegionsOnFS(obj1.getKey(), 
obj1.getValue().toString()))
+      .collect(Collectors.toList());
+  }
+
+  @GET
+  @Path("/orphan-regions-on-rs")
+  public List<HbckOrphanRegionsOnRS> getOrphanRegionsOnRS() {
+    return hbckReport.getOrphanRegionsOnRS().entrySet().stream()
+      .map(obj1 -> new HbckOrphanRegionsOnRS(obj1.getKey(), 
parseServerName(obj1.getValue())))
+      .collect(Collectors.toList());
+  }
+
+  @GET
+  @Path("/inconsistent-regions")
+  public List<HbckInconsistentRegions> getInconsistentRegions() {
+    return hbckReport.getInconsistentRegions().entrySet().stream()
+      .map(obj1 -> new HbckInconsistentRegions(obj1.getKey(),
+        parseServerName(obj1.getValue().getFirst()), 
obj1.getValue().getSecond().stream()
+          .map(this::parseServerName).collect(Collectors.toList())))
+      .collect(Collectors.toList());
+  }
+
+  @GET
+  @Path("/region-holes")
+  public List<HbckRegionHoles> getRegionChainHoles() {
+    return catalogJanitorReport.getHoles().stream()
+      .map(obj1 -> new HbckRegionHoles(parseRegionInfo(obj1.getFirst()),
+        parseRegionInfo(obj1.getSecond())))
+      .collect(Collectors.toList());
+  }
+
+  @GET
+  @Path("/region-overlaps")
+  public List<HbckOverlapRegions> getRegionChainOverlap() {
+    return catalogJanitorReport.getOverlaps().stream()
+      .map(obj1 -> new HbckOverlapRegions(parseRegionInfo(obj1.getFirst()),
+        parseRegionInfo(obj1.getSecond())))
+      .collect(Collectors.toList());
+  }
+
+  @GET
+  @Path("/unknown-servers")
+  public List<HbckUnknownServers> getUnknownServers() {
+    return catalogJanitorReport.getUnknownServers().stream()
+      .map(obj1 -> new HbckUnknownServers(parseRegionInfo(obj1.getFirst()),
+        parseServerName(obj1.getSecond())))
+      .collect(Collectors.toList());
+  }
+
+  @GET
+  @Path("/empty-regioninfo")
+  public List<HbckEmptyRegionInfo> getEmptyRegionInfo() {
+    return catalogJanitorReport.getEmptyRegionInfo().stream()
+      .map(obj1 -> new 
HbckEmptyRegionInfo(Bytes.toString(obj1))).collect(Collectors.toList());
+  }
+
+  public HbckRegionDetails parseRegionInfo(RegionInfo regionInfo) {
+    return new HbckRegionDetails(regionInfo.getEncodedName(),
+      regionInfo.getTable().getNameAsString(), new 
String(regionInfo.getStartKey()),
+      new String(regionInfo.getEndKey()));
+  }
+
+  public HbckServerName parseServerName(ServerName serverName) {
+    return new HbckServerName(serverName.getHostname(), serverName.getPort(),
+      serverName.getStartCode());
+  }
+}
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java
index 8075c558dee..31342089d4d 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java
@@ -39,6 +39,7 @@ import org.apache.hadoop.hbase.client.TableDescriptor;
 import org.apache.hadoop.hbase.executor.ExecutorService;
 import org.apache.hadoop.hbase.favored.FavoredNodesManager;
 import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
+import org.apache.hadoop.hbase.master.hbck.HbckChore;
 import org.apache.hadoop.hbase.master.janitor.CatalogJanitor;
 import org.apache.hadoop.hbase.master.locking.LockManager;
 import org.apache.hadoop.hbase.master.normalizer.RegionNormalizerManager;
@@ -112,6 +113,11 @@ public class MockNoopMasterServices implements 
MasterServices {
     return null;
   }
 
+  @Override
+  public HbckChore getHbckChore() {
+    return null;
+  }
+
   @Override
   public MasterFileSystem getMasterFileSystem() {
     return null;
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/TestHbckMetricsResource.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/TestHbckMetricsResource.java
new file mode 100644
index 00000000000..f977fce45b1
--- /dev/null
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/TestHbckMetricsResource.java
@@ -0,0 +1,422 @@
+/*
+ * 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.StartMiniClusterOption;
+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(
+      
StartMiniClusterOption.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));
+  }
+}

Reply via email to