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.http; 019 020import static org.apache.hadoop.hbase.client.RegionInfoBuilder.FIRST_META_REGIONINFO; 021import static org.hamcrest.MatcherAssert.assertThat; 022import static org.hamcrest.Matchers.allOf; 023import static org.hamcrest.Matchers.containsString; 024import static org.hamcrest.Matchers.endsWith; 025import static org.hamcrest.Matchers.startsWith; 026import static org.junit.Assert.assertThrows; 027import static org.mockito.Mockito.mock; 028import static org.mockito.Mockito.when; 029 030import java.time.Instant; 031import java.util.ArrayList; 032import java.util.Arrays; 033import java.util.HashMap; 034import java.util.List; 035import java.util.Map; 036import java.util.Objects; 037import java.util.concurrent.CompletableFuture; 038import java.util.function.Supplier; 039import org.apache.hadoop.conf.Configuration; 040import org.apache.hadoop.fs.Path; 041import org.apache.hadoop.hbase.ConnectionRule; 042import org.apache.hadoop.hbase.HBaseClassTestRule; 043import org.apache.hadoop.hbase.HBaseConfiguration; 044import org.apache.hadoop.hbase.HConstants; 045import org.apache.hadoop.hbase.MiniClusterRule; 046import org.apache.hadoop.hbase.ServerName; 047import org.apache.hadoop.hbase.StartTestingClusterOption; 048import org.apache.hadoop.hbase.TableName; 049import org.apache.hadoop.hbase.client.AsyncAdmin; 050import org.apache.hadoop.hbase.client.AsyncConnection; 051import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 052import org.apache.hadoop.hbase.client.Durability; 053import org.apache.hadoop.hbase.client.RegionInfo; 054import org.apache.hadoop.hbase.client.TableDescriptor; 055import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 056import org.apache.hadoop.hbase.master.HMaster; 057import org.apache.hadoop.hbase.master.hbck.HbckChore; 058import org.apache.hadoop.hbase.master.hbck.HbckReport; 059import org.apache.hadoop.hbase.master.http.hbck.resource.HbckMetricsResource; 060import org.apache.hadoop.hbase.master.janitor.CatalogJanitor; 061import org.apache.hadoop.hbase.master.janitor.CatalogJanitorReport; 062import org.apache.hadoop.hbase.testclassification.LargeTests; 063import org.apache.hadoop.hbase.testclassification.MasterTests; 064import org.apache.hadoop.hbase.util.Bytes; 065import org.apache.hadoop.hbase.util.Pair; 066import org.junit.ClassRule; 067import org.junit.Test; 068import org.junit.experimental.categories.Category; 069import org.junit.rules.ExternalResource; 070import org.junit.rules.RuleChain; 071import org.mockito.Mockito; 072import org.slf4j.Logger; 073import org.slf4j.LoggerFactory; 074 075import org.apache.hbase.thirdparty.javax.ws.rs.NotAcceptableException; 076import org.apache.hbase.thirdparty.javax.ws.rs.client.Client; 077import org.apache.hbase.thirdparty.javax.ws.rs.client.ClientBuilder; 078import org.apache.hbase.thirdparty.javax.ws.rs.client.WebTarget; 079import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType; 080 081/** 082 * Tests for the {@link HbckMetricsResource}. 083 */ 084@Category({ MasterTests.class, LargeTests.class }) 085public class TestHbckMetricsResource { 086 087 private static final Logger LOG = LoggerFactory.getLogger(TestHbckMetricsResource.class); 088 089 // Test data for Mock HBCK Report 090 private static final long reportStartTime = 123456789000L; 091 private static final long reportEndTime = 234567890000L; 092 private static final String regionId1 = "regionId1"; 093 private static final String regionId2 = "regionId2"; 094 private static final String localhost1 = "localhost1"; 095 private static final String localhost2 = "localhost2"; 096 private static final String port = "16010"; 097 private static final String hostStartCode = "123456789"; 098 private static final String path1 = "hdfs://path1"; 099 private static final String path2 = "hdfs://path2"; 100 private static final String metaRegionID = FIRST_META_REGIONINFO.getEncodedName(); 101 private static final String metaTableName = FIRST_META_REGIONINFO.getTable().getNameAsString(); 102 103 // Various Keys in HBCK JSON Response. 104 private static final String quoteColon = "\":"; 105 private static final String quote = "\""; 106 private static final String regionId = quote + "region_id" + quoteColon; 107 private static final String regionHdfsPath = quote + "region_hdfs_path" + quoteColon; 108 private static final String rsName = quote + "rs_name" + quoteColon; 109 private static final String hostName = quote + "host_name" + quoteColon; 110 private static final String hostPort = quote + "host_port" + quoteColon; 111 private static final String startCode = quote + "start_code" + quoteColon; 112 private static final String serverNameInMeta = quote + "server_name_in_meta" + quoteColon; 113 private static final String listOfServers = quote + "list_of_servers" + quoteColon; 114 private static final String region1Info = quote + "region1_info" + quoteColon; 115 private static final String region2Info = quote + "region2_info" + quoteColon; 116 private static final String regionInfo = quote + "region_info" + quoteColon; 117 private static final String serverName = quote + "server_name" + quoteColon; 118 private static final String tableName = quote + "table_name" + quoteColon; 119 120 private static final String dataStartsWith = "{\"data\":["; 121 private static final String dataEndsWith = "]}"; 122 private static final String hbckReportStartTime = quote + "hbck_report_start_time" + quoteColon; 123 private static final String hbckReportEndTime = quote + "hbck_report_end_time" + quoteColon; 124 private static final String hbckOrphanRegionOnFS = 125 quote + "hbck_orphan_regions_on_fs" + quoteColon; 126 private static final String hbckOrphanRegionOnRS = 127 quote + "hbck_orphan_regions_on_rs" + quoteColon; 128 private static final String hbckInconsistentRegion = 129 quote + "hbck_inconsistent_regions" + quoteColon; 130 private static final String hbckHoles = quote + "hbck_holes" + quoteColon; 131 private static final String hbckOverlaps = quote + "hbck_overlaps" + quoteColon; 132 private static final String hbckUnknownServers = quote + "hbck_unknown_servers" + quoteColon; 133 private static final String hbckEmptyRegionInfo = quote + "hbck_empty_region_info" + quoteColon; 134 135 @ClassRule 136 public static final HBaseClassTestRule CLASS_RULE = 137 HBaseClassTestRule.forClass(TestHbckMetricsResource.class); 138 139 private static final MiniClusterRule miniClusterRule = MiniClusterRule.newBuilder() 140 .setMiniClusterOption( 141 StartTestingClusterOption.builder().numZkServers(3).numMasters(3).numDataNodes(3).build()) 142 .setConfiguration(() -> { 143 // enable Master InfoServer and random port selection 144 final Configuration conf = HBaseConfiguration.create(); 145 conf.setInt(HConstants.MASTER_INFO_PORT, 0); 146 conf.set("hbase.http.jersey.tracing.type", "ON_DEMAND"); 147 return conf; 148 }).build(); 149 150 private static final ConnectionRule connectionRule = 151 ConnectionRule.createAsyncConnectionRule(miniClusterRule::createAsyncConnection); 152 private static final ClassSetup classRule = new ClassSetup(connectionRule::getAsyncConnection); 153 154 private static final class ClassSetup extends ExternalResource { 155 156 private final Supplier<AsyncConnection> connectionSupplier; 157 private final TableName tableName; 158 private AsyncAdmin admin; 159 private WebTarget target; 160 161 public ClassSetup(final Supplier<AsyncConnection> connectionSupplier) { 162 this.connectionSupplier = connectionSupplier; 163 tableName = TableName.valueOf(TestHbckMetricsResource.class.getSimpleName()); 164 } 165 166 public WebTarget getTarget() { 167 return target; 168 } 169 170 @Override 171 protected void before() throws Throwable { 172 final AsyncConnection conn = connectionSupplier.get(); 173 admin = conn.getAdmin(); 174 final TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(tableName) 175 .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("c")).build()) 176 .setDurability(Durability.SKIP_WAL).build(); 177 admin.createTable(tableDescriptor).get(); 178 179 HMaster master = miniClusterRule.getTestingUtility().getMiniHBaseCluster().getMaster(); 180 181 HbckChore hbckChore = mock(HbckChore.class); 182 HbckReport hbckReport = mock(HbckReport.class); 183 CatalogJanitor catalogJanitorChore = mock(CatalogJanitor.class); 184 CatalogJanitorReport catalogJanitorReport = mock(CatalogJanitorReport.class); 185 master.setHbckChoreForTesting(hbckChore); 186 master.setCatalogJanitorChoreForTesting(catalogJanitorChore); 187 188 // Test data for Mock HBCK Report 189 ServerName server1 = 190 ServerName.valueOf(localhost1, Integer.parseInt(port), Integer.parseInt(hostStartCode)); 191 ServerName server2 = 192 ServerName.valueOf(localhost2, Integer.parseInt(port), Integer.parseInt(hostStartCode)); 193 Path hdfsPath1 = new Path(path1); 194 Path hdfsPath2 = new Path(path2); 195 196 // Orphan on RS Test data 197 Map<String, ServerName> mapOfOrphanRegionsOnRS = new HashMap<>(); 198 mapOfOrphanRegionsOnRS.put(regionId1, server1); 199 mapOfOrphanRegionsOnRS.put(regionId2, server2); 200 201 // Orphan Region on FS Test Data 202 Map<String, Path> mapOfOrphanRegionOnFS = new HashMap<>(); 203 mapOfOrphanRegionOnFS.put(regionId1, hdfsPath1); 204 mapOfOrphanRegionOnFS.put(regionId2, hdfsPath2); 205 206 // Inconsistent Regions Test Data 207 Map<String, Pair<ServerName, List<ServerName>>> mapOfInconsistentRegions = new HashMap<>(); 208 mapOfInconsistentRegions.put(regionId1, new Pair<>(server1, Arrays.asList(server1, server2))); 209 mapOfInconsistentRegions.put(regionId2, new Pair<>(server2, Arrays.asList(server1, server2))); 210 211 // Region Overlap and Region Holes Test Data 212 List<Pair<RegionInfo, RegionInfo>> listOfRegion = new ArrayList<>(); 213 listOfRegion.add(new Pair<>(FIRST_META_REGIONINFO, FIRST_META_REGIONINFO)); 214 listOfRegion.add(new Pair<>(FIRST_META_REGIONINFO, FIRST_META_REGIONINFO)); 215 216 // Unknown RegionServer Test Data 217 List<Pair<RegionInfo, ServerName>> listOfUnknownServers = new ArrayList<>(); 218 listOfUnknownServers.add(new Pair<>(FIRST_META_REGIONINFO, server1)); 219 listOfUnknownServers.add(new Pair<>(FIRST_META_REGIONINFO, server2)); 220 221 // Empty Region Info Test Data 222 List<byte[]> listOfEmptyRegionInfo = new ArrayList<>(); 223 listOfEmptyRegionInfo.add(regionId1.getBytes()); 224 listOfEmptyRegionInfo.add(regionId2.getBytes()); 225 226 // Mock HBCK Report and CatalogJanitor Report 227 when(hbckReport.getCheckingStartTimestamp()) 228 .thenReturn(Instant.ofEpochMilli(reportStartTime)); 229 when(hbckReport.getCheckingEndTimestamp()).thenReturn(Instant.ofEpochSecond(reportEndTime)); 230 when(hbckReport.getOrphanRegionsOnFS()).thenReturn(mapOfOrphanRegionOnFS); 231 when(hbckReport.getOrphanRegionsOnRS()).thenReturn(mapOfOrphanRegionsOnRS); 232 when(hbckReport.getInconsistentRegions()).thenReturn(mapOfInconsistentRegions); 233 when(catalogJanitorReport.getHoles()).thenReturn(listOfRegion); 234 when(catalogJanitorReport.getOverlaps()).thenReturn(listOfRegion); 235 when(catalogJanitorReport.getUnknownServers()).thenReturn(listOfUnknownServers); 236 when(catalogJanitorReport.getEmptyRegionInfo()).thenReturn(listOfEmptyRegionInfo); 237 238 Mockito.doReturn(hbckReport).when(hbckChore).getLastReport(); 239 Mockito.doReturn(catalogJanitorReport).when(catalogJanitorChore).getLastReport(); 240 241 final String baseUrl = 242 admin.getMaster().thenApply(ServerName::getHostname).thenCombine(admin.getMasterInfoPort(), 243 (hostName, infoPort) -> "http://" + hostName + ":" + infoPort).get(); 244 final Client client = ClientBuilder.newClient(); 245 target = client.target(baseUrl).path("hbck/hbck-metrics"); 246 } 247 248 @Override 249 protected void after() { 250 final TableName tableName = TableName.valueOf("test"); 251 try { 252 admin.tableExists(tableName).thenCompose(val -> { 253 if (val) { 254 return admin.disableTable(tableName) 255 .thenCompose(ignored -> admin.deleteTable(tableName)); 256 } else { 257 return CompletableFuture.completedFuture(null); 258 } 259 }).get(); 260 } catch (Exception e) { 261 throw new RuntimeException(e); 262 } 263 } 264 } 265 266 @ClassRule 267 public static RuleChain ruleChain = 268 RuleChain.outerRule(miniClusterRule).around(connectionRule).around(classRule); 269 270 @Test 271 public void testGetRoot() { 272 final String response = classRule.getTarget().request(MediaType.APPLICATION_JSON_TYPE) 273 .header("X-Jersey-Tracing-Accept", true).get(String.class); 274 LOG.info("HBCK JSON Response : " + response); 275 assertThat(response, 276 allOf(containsString(hbckReportStartTime), containsString(hbckReportEndTime), 277 containsString(hbckOrphanRegionOnFS), containsString(hbckOrphanRegionOnRS), 278 containsString(hbckInconsistentRegion), containsString(hbckHoles), 279 containsString(hbckOverlaps), containsString(hbckUnknownServers), 280 containsString(hbckEmptyRegionInfo), containsString(Objects.toString(reportStartTime)), 281 containsString(Objects.toString(reportEndTime)))); 282 } 283 284 @Test 285 public void testGetRootHtml() { 286 assertThrows(NotAcceptableException.class, () -> classRule.getTarget() 287 .request(MediaType.TEXT_HTML_TYPE).header("X-Jersey-Tracing-Accept", true).get(String.class)); 288 } 289 290 @Test 291 public void testGetOrphanRegionOnFS() { 292 final String response = 293 classRule.getTarget().path("orphan-regions-on-fs").request(MediaType.APPLICATION_JSON_TYPE) 294 .header("X-Jersey-Tracing-Accept", true).get(String.class); 295 LOG.info("HBCK Response for resource orphan-regions-on-fs : " + response); 296 assertThat(response, 297 allOf(startsWith(dataStartsWith), endsWith(dataEndsWith), containsString(regionId), 298 containsString(regionHdfsPath), containsString(regionId1), containsString(regionId2), 299 containsString(path1), containsString(path2))); 300 } 301 302 @Test 303 public void testGetOrphanRegionOnFSHtml() { 304 assertThrows(NotAcceptableException.class, 305 () -> classRule.getTarget().path("orphan-regions-on-fs").request(MediaType.TEXT_HTML_TYPE) 306 .header("X-Jersey-Tracing-Accept", true).get(String.class)); 307 } 308 309 @Test 310 public void testGetOrphanRegionOnRS() { 311 final String response = 312 classRule.getTarget().path("orphan-regions-on-rs").request(MediaType.APPLICATION_JSON_TYPE) 313 .header("X-Jersey-Tracing-Accept", true).get(String.class); 314 LOG.info("HBCK Response for resource orphan-regions-on-rs : " + response); 315 assertThat(response, 316 allOf(startsWith(dataStartsWith), endsWith(dataEndsWith), containsString(regionId), 317 containsString(rsName), containsString(hostName), containsString(hostPort), 318 containsString(startCode), containsString(regionId1), containsString(regionId2), 319 containsString(localhost1), containsString(localhost2), containsString(port), 320 containsString(hostStartCode))); 321 } 322 323 @Test 324 public void testGetOrphanRegionOnRSHtml() { 325 assertThrows(NotAcceptableException.class, 326 () -> classRule.getTarget().path("orphan-regions-on-rs").request(MediaType.TEXT_HTML_TYPE) 327 .header("X-Jersey-Tracing-Accept", true).get(String.class)); 328 } 329 330 @Test 331 public void testGetInconsistentRegions() { 332 final String response = 333 classRule.getTarget().path("inconsistent-regions").request(MediaType.APPLICATION_JSON_TYPE) 334 .header("X-Jersey-Tracing-Accept", true).get(String.class); 335 LOG.info("HBCK Response for resource inconsistent-regions : " + response); 336 assertThat(response, 337 allOf(startsWith(dataStartsWith), endsWith(dataEndsWith), containsString(hostName), 338 containsString(hostPort), containsString(startCode), containsString(listOfServers), 339 containsString(regionId1), containsString(regionId2), containsString(regionId), 340 containsString(serverNameInMeta), containsString(localhost1), containsString(localhost2), 341 containsString(port), containsString(hostStartCode))); 342 } 343 344 @Test 345 public void testGetInconsistentRegionsHtml() { 346 assertThrows(NotAcceptableException.class, 347 () -> classRule.getTarget().path("inconsistent-regions").request(MediaType.TEXT_HTML_TYPE) 348 .header("X-Jersey-Tracing-Accept", true).get(String.class)); 349 } 350 351 @Test 352 public void testGetRegionHoles() { 353 final String response = 354 classRule.getTarget().path("region-holes").request(MediaType.APPLICATION_JSON_TYPE) 355 .header("X-Jersey-Tracing-Accept", true).get(String.class); 356 LOG.info("HBCK Response for resource region-holes : " + response); 357 assertThat(response, 358 allOf(startsWith(dataStartsWith), endsWith(dataEndsWith), containsString(region1Info), 359 containsString(region2Info), containsString(regionId), containsString(tableName), 360 containsString(metaRegionID), containsString(metaTableName))); 361 } 362 363 @Test 364 public void testGetRegionHolesHtml() { 365 assertThrows(NotAcceptableException.class, () -> classRule.getTarget().path("region-holes") 366 .request(MediaType.TEXT_HTML_TYPE).header("X-Jersey-Tracing-Accept", true).get(String.class)); 367 } 368 369 @Test 370 public void testGetRegionOverlaps() { 371 final String response = 372 classRule.getTarget().path("region-overlaps").request(MediaType.APPLICATION_JSON_TYPE) 373 .header("X-Jersey-Tracing-Accept", true).get(String.class); 374 LOG.info("HBCK Response for resource region-overlaps : " + response); 375 assertThat(response, 376 allOf(startsWith(dataStartsWith), endsWith(dataEndsWith), containsString(regionId), 377 containsString(tableName), containsString(region2Info), containsString(region2Info), 378 containsString(metaRegionID), containsString(metaTableName))); 379 } 380 381 @Test 382 public void testGetRegionOverlapsHtml() { 383 assertThrows(NotAcceptableException.class, () -> classRule.getTarget().path("region-overlaps") 384 .request(MediaType.TEXT_HTML_TYPE).header("X-Jersey-Tracing-Accept", true).get(String.class)); 385 } 386 387 @Test 388 public void testGetUnkownServers() { 389 final String response = 390 classRule.getTarget().path("unknown-servers").request(MediaType.APPLICATION_JSON_TYPE) 391 .header("X-Jersey-Tracing-Accept", true).get(String.class); 392 LOG.info("HBCK Response for resource unknown-servers : " + response); 393 assertThat(response, 394 allOf(startsWith(dataStartsWith), endsWith(dataEndsWith), containsString(regionInfo), 395 containsString(regionId), containsString(tableName), containsString(serverName), 396 containsString(serverName), containsString(port), containsString(startCode), 397 containsString(metaRegionID), containsString(metaTableName), containsString(localhost1), 398 containsString(localhost2), containsString(port), containsString(startCode))); 399 } 400 401 @Test 402 public void testGetUnkownServersHtml() { 403 assertThrows(NotAcceptableException.class, () -> classRule.getTarget().path("unknown-servers") 404 .request(MediaType.TEXT_HTML_TYPE).header("X-Jersey-Tracing-Accept", true).get(String.class)); 405 } 406 407 @Test 408 public void testGetEmptyRegionInfo() { 409 final String response = 410 classRule.getTarget().path("empty-regioninfo").request(MediaType.APPLICATION_JSON_TYPE) 411 .header("X-Jersey-Tracing-Accept", true).get(String.class); 412 LOG.info("HBCK Response for resource empty-regioninfo : " + response); 413 assertThat(response, allOf(startsWith(dataStartsWith), endsWith(dataEndsWith), 414 containsString(regionInfo), containsString(regionId1), containsString(regionId2))); 415 } 416 417 @Test 418 public void testGetEmptyRegionInfoHtml() { 419 assertThrows(NotAcceptableException.class, () -> classRule.getTarget().path("empty-regioninfo") 420 .request(MediaType.TEXT_HTML_TYPE).header("X-Jersey-Tracing-Accept", true).get(String.class)); 421 } 422}