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}