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.hamcrest.MatcherAssert.assertThat;
021import static org.hamcrest.Matchers.allOf;
022import static org.hamcrest.Matchers.containsString;
023import static org.hamcrest.Matchers.endsWith;
024import static org.hamcrest.Matchers.startsWith;
025import static org.junit.jupiter.api.Assertions.assertThrows;
026
027import java.util.concurrent.CompletableFuture;
028import java.util.function.Supplier;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.hbase.ConnectionExtension;
031import org.apache.hadoop.hbase.HBaseConfiguration;
032import org.apache.hadoop.hbase.HConstants;
033import org.apache.hadoop.hbase.MiniClusterExtension;
034import org.apache.hadoop.hbase.ServerName;
035import org.apache.hadoop.hbase.StartTestingClusterOption;
036import org.apache.hadoop.hbase.TableName;
037import org.apache.hadoop.hbase.client.AsyncAdmin;
038import org.apache.hadoop.hbase.client.AsyncConnection;
039import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
040import org.apache.hadoop.hbase.client.Durability;
041import org.apache.hadoop.hbase.client.TableDescriptor;
042import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
043import org.apache.hadoop.hbase.master.http.api_v1.cluster_metrics.resource.ClusterMetricsResource;
044import org.apache.hadoop.hbase.testclassification.LargeTests;
045import org.apache.hadoop.hbase.testclassification.MasterTests;
046import org.apache.hadoop.hbase.util.Bytes;
047import org.junit.jupiter.api.Order;
048import org.junit.jupiter.api.Tag;
049import org.junit.jupiter.api.Test;
050import org.junit.jupiter.api.extension.AfterAllCallback;
051import org.junit.jupiter.api.extension.BeforeAllCallback;
052import org.junit.jupiter.api.extension.ExtensionContext;
053import org.junit.jupiter.api.extension.RegisterExtension;
054
055import org.apache.hbase.thirdparty.javax.ws.rs.NotAcceptableException;
056import org.apache.hbase.thirdparty.javax.ws.rs.client.Client;
057import org.apache.hbase.thirdparty.javax.ws.rs.client.ClientBuilder;
058import org.apache.hbase.thirdparty.javax.ws.rs.client.WebTarget;
059import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType;
060
061/**
062 * Tests for the master api_v1 {@link ClusterMetricsResource}.
063 */
064@Tag(MasterTests.TAG)
065@Tag(LargeTests.TAG)
066public class TestApiV1ClusterMetricsResource {
067
068  @Order(1)
069  @RegisterExtension
070  private static final MiniClusterExtension miniClusterExtension = MiniClusterExtension.newBuilder()
071    .setMiniClusterOption(
072      StartTestingClusterOption.builder().numZkServers(3).numMasters(3).numDataNodes(3).build())
073    .setConfiguration(() -> {
074      // enable Master InfoServer and random port selection
075      final Configuration conf = HBaseConfiguration.create();
076      conf.setInt(HConstants.MASTER_INFO_PORT, 0);
077      conf.set("hbase.http.jersey.tracing.type", "ON_DEMAND");
078      return conf;
079    }).build();
080
081  @Order(2)
082  @RegisterExtension
083  private static final ConnectionExtension connectionExtension =
084    ConnectionExtension.createAsyncConnectionExtension(miniClusterExtension::createAsyncConnection);
085
086  @Order(3)
087  @RegisterExtension
088  private static final ClassSetupExtension classRule =
089    new ClassSetupExtension(connectionExtension::getAsyncConnection);
090
091  private static final class ClassSetupExtension implements BeforeAllCallback, AfterAllCallback {
092
093    private final TableName tableName;
094    private AsyncAdmin admin;
095    private WebTarget target;
096    private final Supplier<AsyncConnection> connectionSupplier;
097
098    public ClassSetupExtension(final Supplier<AsyncConnection> connectionSupplier) {
099      this.connectionSupplier = connectionSupplier;
100      tableName = TableName.valueOf(TestApiV1ClusterMetricsResource.class.getSimpleName());
101    }
102
103    public WebTarget getTarget() {
104      return target;
105    }
106
107    @Override
108    public void beforeAll(ExtensionContext context) throws Exception {
109      final AsyncConnection conn = connectionSupplier.get();
110      admin = conn.getAdmin();
111      final ColumnFamilyDescriptorBuilder cfBuilder =
112        ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("c"));
113      final TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(tableName)
114        .setColumnFamily(cfBuilder.build()).setDurability(Durability.SKIP_WAL).build();
115      admin.createTable(tableDescriptor).get();
116
117      final String baseUrl =
118        admin.getMaster().thenApply(ServerName::getHostname).thenCombine(admin.getMasterInfoPort(),
119          (hostName, infoPort) -> "http://" + hostName + ":" + infoPort).get();
120      final Client client = ClientBuilder.newClient();
121      target = client.target(baseUrl).path("api/v1/admin/cluster_metrics");
122    }
123
124    @Override
125    public void afterAll(ExtensionContext context) {
126      final TableName tableName = TableName.valueOf("test");
127      try {
128        admin.tableExists(tableName).thenCompose(val -> {
129          if (val) {
130            return admin.disableTable(tableName)
131              .thenCompose(ignored -> admin.deleteTable(tableName));
132          } else {
133            return CompletableFuture.completedFuture(null);
134          }
135        }).get();
136      } catch (Exception e) {
137        throw new RuntimeException(e);
138      }
139    }
140  }
141
142  @Test
143  public void testGetRoot() {
144    final String response = classRule.getTarget().request(MediaType.APPLICATION_JSON_TYPE)
145      .header("X-Jersey-Tracing-Accept", true).get(String.class);
146    assertThat(response,
147      allOf(containsString("\"hbase_version\":"), containsString("\"cluster_id\":"),
148        containsString("\"master_name\":"), containsString("\"backup_master_names\":")));
149  }
150
151  @Test
152  public void testGetRootHtml() {
153    assertThrows(NotAcceptableException.class, () -> classRule.getTarget()
154      .request(MediaType.TEXT_HTML_TYPE).header("X-Jersey-Tracing-Accept", true).get(String.class));
155  }
156
157  @Test
158  public void testGetLiveServers() {
159    final String response =
160      classRule.getTarget().path("live_servers").request(MediaType.APPLICATION_JSON_TYPE)
161        .header("X-Jersey-Tracing-Accept", true).get(String.class);
162    assertThat(response, allOf(startsWith("{\"data\":["), endsWith("]}")));
163  }
164
165  @Test
166  public void testGetLiveServersHtml() {
167    assertThrows(NotAcceptableException.class, () -> classRule.getTarget().path("live_servers")
168      .request(MediaType.TEXT_HTML_TYPE).header("X-Jersey-Tracing-Accept", true).get(String.class));
169  }
170
171  @Test
172  public void testGetDeadServers() {
173    final String response =
174      classRule.getTarget().path("dead_servers").request(MediaType.APPLICATION_JSON_TYPE)
175        .header("X-Jersey-Tracing-Accept", true).get(String.class);
176    assertThat(response, allOf(startsWith("{\"data\":["), endsWith("]}")));
177  }
178
179  @Test
180  public void testGetDeadServersHtml() {
181    assertThrows(NotAcceptableException.class, () -> classRule.getTarget().path("dead_servers")
182      .request(MediaType.TEXT_HTML_TYPE).header("X-Jersey-Tracing-Accept", true).get(String.class));
183  }
184}