You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by nd...@apache.org on 2022/03/14 16:28:12 UTC
[hbase] branch branch-2 updated (50e1230 -> e6879b8)
This is an automated email from the ASF dual-hosted git repository.
ndimiduk pushed a change to branch branch-2
in repository https://gitbox.apache.org/repos/asf/hbase.git.
from 50e1230 HBASE-26670 HFileLinkCleaner should be added even if snapshot is disabled (#4032)
new cdabd6a HBASE-25895 Implement a Cluster Metrics JSON endpoint
new e6879b8 HBASE-25895 Implement a Cluster Metrics JSON endpoint (addendum)
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../org/apache/hadoop/hbase/http/HttpServer.java | 58 +++++-
.../org/apache/hadoop/hbase/http/InfoServer.java | 19 +-
.../hbase/http/gson/ByteArraySerializer.java | 22 ++-
.../hbase/http/gson/GsonMessageBodyWriter.java | 99 ++++++++++
.../hbase/http/jersey/ResponseEntityMapper.java | 53 ++++++
.../hbase/http/jersey/SupplierFactoryAdapter.java | 30 ++-
.../org/apache/hadoop/hbase/master/HMaster.java | 13 +-
.../master/http/api_v1/ResourceConfigFactory.java | 59 ++++++
.../cluster_metrics/model/ClusterMetrics.java | 70 +++++++
.../http/api_v1/cluster_metrics/package-info.java | 16 +-
.../resource/ClusterMetricsResource.java | 88 +++++++++
.../http/gson/GsonFactory.java} | 38 ++--
.../master/http/gson/GsonSerializationFeature.java | 67 +++++++
.../master/http/gson/SizeAsBytesSerializer.java | 22 ++-
.../hbase/master/http/gson}/package-info.java | 9 +-
.../hbase/master/http/jersey/MasterFeature.java | 73 +++++++
.../org/apache/hadoop/hbase/MiniClusterRule.java | 5 +
.../http/TestApiV1ClusterMetricsResource.java | 211 +++++++++++++++++++++
.../hbase/master/http/gson/GsonFactoryTest.java | 105 ++++++++++
19 files changed, 983 insertions(+), 74 deletions(-)
copy hbase-procedure/src/main/java/org/apache/hadoop/hbase/procedure2/LockedResourceType.java => hbase-http/src/main/java/org/apache/hadoop/hbase/http/gson/ByteArraySerializer.java (59%)
create mode 100644 hbase-http/src/main/java/org/apache/hadoop/hbase/http/gson/GsonMessageBodyWriter.java
create mode 100644 hbase-http/src/main/java/org/apache/hadoop/hbase/http/jersey/ResponseEntityMapper.java
copy hbase-common/src/main/java/org/apache/hadoop/hbase/util/SimpleMovingAverage.java => hbase-http/src/main/java/org/apache/hadoop/hbase/http/jersey/SupplierFactoryAdapter.java (60%)
create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/api_v1/ResourceConfigFactory.java
create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/api_v1/cluster_metrics/model/ClusterMetrics.java
copy hbase-hadoop-compat/src/main/java/org/apache/hadoop/hbase/master/MetricsMasterProcSourceFactory.java => hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/api_v1/cluster_metrics/package-info.java (76%)
create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/api_v1/cluster_metrics/resource/ClusterMetricsResource.java
copy hbase-server/src/main/java/org/apache/hadoop/hbase/{regionserver/BasicMemStoreCompactionStrategy.java => master/http/gson/GsonFactory.java} (54%)
create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/gson/GsonSerializationFeature.java
copy hbase-procedure/src/main/java/org/apache/hadoop/hbase/procedure2/LockedResourceType.java => hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/gson/SizeAsBytesSerializer.java (58%)
copy {hbase-archetypes/hbase-shaded-client-project/src/main/java/org/apache/hbase/archetypes/exemplars/shaded_client => hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/gson}/package-info.java (70%)
create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/jersey/MasterFeature.java
create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/TestApiV1ClusterMetricsResource.java
create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/gson/GsonFactoryTest.java
[hbase] 01/02: HBASE-25895 Implement a Cluster Metrics JSON endpoint
Posted by nd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
ndimiduk pushed a commit to branch branch-2
in repository https://gitbox.apache.org/repos/asf/hbase.git
commit cdabd6a1add8c1331cfc4ffa0f04bf06fa1110df
Author: Nick Dimiduk <nd...@apache.org>
AuthorDate: Fri May 14 18:07:03 2021 -0700
HBASE-25895 Implement a Cluster Metrics JSON endpoint
Publishes a set of JSON endpoints following a RESTful structure, which expose a subset of the
`o.a.h.h.ClusterMetrics` object tree. The URI structure is as follows
/api/v1/admin/cluster_metrics
/api/v1/admin/cluster_metrics/live_servers
/api/v1/admin/cluster_metrics/dead_servers
Signed-off-by: Sean Busbey <bu...@apache.org>
Signed-off-by: Andrew Purtell <ap...@apache.org>
---
.../org/apache/hadoop/hbase/http/HttpServer.java | 58 +++++-
.../org/apache/hadoop/hbase/http/InfoServer.java | 19 +-
.../hbase/http/gson/ByteArraySerializer.java | 38 ++++
.../hbase/http/gson/GsonMessageBodyWriter.java | 99 ++++++++++
.../hbase/http/jersey/ResponseEntityMapper.java | 53 ++++++
.../hbase/http/jersey/SupplierFactoryAdapter.java | 42 +++++
.../org/apache/hadoop/hbase/master/HMaster.java | 13 +-
.../master/http/api_v1/ResourceConfigFactory.java | 59 ++++++
.../cluster_metrics/model/ClusterMetrics.java | 70 +++++++
.../http/api_v1/cluster_metrics/package-info.java | 27 +++
.../resource/ClusterMetricsResource.java | 88 +++++++++
.../hadoop/hbase/master/http/gson/GsonFactory.java | 43 +++++
.../master/http/gson/GsonSerializationFeature.java | 67 +++++++
.../master/http/gson/SizeAsBytesSerializer.java | 38 ++++
.../hbase/master/http/gson/package-info.java | 24 +++
.../hbase/master/http/jersey/MasterFeature.java | 73 ++++++++
.../org/apache/hadoop/hbase/MiniClusterRule.java | 5 +
.../http/TestApiV1ClusterMetricsResource.java | 203 +++++++++++++++++++++
.../hbase/master/http/gson/GsonFactoryTest.java | 105 +++++++++++
19 files changed, 1113 insertions(+), 11 deletions(-)
diff --git a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java
index 884780c..f8c04ba 100644
--- a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java
+++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java
@@ -39,6 +39,7 @@ import java.util.stream.Collectors;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
+import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
@@ -839,6 +840,17 @@ public class HttpServer implements FilterContainer {
}
/**
+ * Adds a servlet in the server that any user can access. This method differs from
+ * {@link #addPrivilegedServlet(String, ServletHolder)} in that any authenticated user
+ * can interact with the servlet added by this method.
+ * @param pathSpec The path spec for the servlet
+ * @param holder The servlet holder
+ */
+ public void addUnprivilegedServlet(String pathSpec, ServletHolder holder) {
+ addServletWithAuth(pathSpec, holder, false);
+ }
+
+ /**
* Adds a servlet in the server that only administrators can access. This method differs from
* {@link #addUnprivilegedServlet(String, String, Class)} in that only those authenticated user
* who are identified as administrators can interact with the servlet added by this method.
@@ -849,6 +861,16 @@ public class HttpServer implements FilterContainer {
}
/**
+ * Adds a servlet in the server that only administrators can access. This method differs from
+ * {@link #addUnprivilegedServlet(String, ServletHolder)} in that only those
+ * authenticated user who are identified as administrators can interact with the servlet added by
+ * this method.
+ */
+ public void addPrivilegedServlet(String pathSpec, ServletHolder holder) {
+ addServletWithAuth(pathSpec, holder, true);
+ }
+
+ /**
* Internal method to add a servlet to the HTTP server. Developers should not call this method
* directly, but invoke it via {@link #addUnprivilegedServlet(String, String, Class)} or
* {@link #addPrivilegedServlet(String, String, Class)}.
@@ -860,6 +882,16 @@ public class HttpServer implements FilterContainer {
}
/**
+ * Internal method to add a servlet to the HTTP server. Developers should not call this method
+ * directly, but invoke it via {@link #addUnprivilegedServlet(String, ServletHolder)} or
+ * {@link #addPrivilegedServlet(String, ServletHolder)}.
+ */
+ void addServletWithAuth(String pathSpec, ServletHolder holder, boolean requireAuthz) {
+ addInternalServlet(pathSpec, holder, requireAuthz);
+ addFilterPathMapping(pathSpec, webAppContext);
+ }
+
+ /**
* Add an internal servlet in the server, specifying whether or not to
* protect with Kerberos authentication.
* Note: This method is to be used for adding servlets that facilitate
@@ -867,17 +899,33 @@ public class HttpServer implements FilterContainer {
* servlets added using this method, filters (except internal Kerberos
* filters) are not enabled.
*
- * @param name The name of the servlet (can be passed as null)
- * @param pathSpec The path spec for the servlet
- * @param clazz The servlet class
- * @param requireAuth Require Kerberos authenticate to access servlet
+ * @param name The name of the {@link Servlet} (can be passed as null)
+ * @param pathSpec The path spec for the {@link Servlet}
+ * @param clazz The {@link Servlet} class
+ * @param requireAuthz Require Kerberos authenticate to access servlet
*/
void addInternalServlet(String name, String pathSpec,
- Class<? extends HttpServlet> clazz, boolean requireAuthz) {
+ Class<? extends HttpServlet> clazz, boolean requireAuthz) {
ServletHolder holder = new ServletHolder(clazz);
if (name != null) {
holder.setName(name);
}
+ addInternalServlet(pathSpec, holder, requireAuthz);
+ }
+
+ /**
+ * Add an internal servlet in the server, specifying whether or not to
+ * protect with Kerberos authentication.
+ * Note: This method is to be used for adding servlets that facilitate
+ * internal communication and not for user facing functionality. For
+ * servlets added using this method, filters (except internal Kerberos
+ * filters) are not enabled.
+ *
+ * @param pathSpec The path spec for the {@link Servlet}
+ * @param holder The object providing the {@link Servlet} instance
+ * @param requireAuthz Require Kerberos authenticate to access servlet
+ */
+ void addInternalServlet(String pathSpec, ServletHolder holder, boolean requireAuthz) {
if (authenticationEnabled && requireAuthz) {
FilterHolder filter = new FilterHolder(AdminAuthorizedFilter.class);
filter.setName(AdminAuthorizedFilter.class.getSimpleName());
diff --git a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/InfoServer.java b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/InfoServer.java
index 5ed380e..8b13e2b 100644
--- a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/InfoServer.java
+++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/InfoServer.java
@@ -1,4 +1,4 @@
-/**
+/*
* 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
@@ -19,18 +19,16 @@ package org.apache.hadoop.hbase.http;
import java.io.IOException;
import java.net.URI;
-
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
-
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.CommonConfigurationKeys;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.security.authorize.AccessControlList;
import org.apache.yetus.audience.InterfaceAudience;
-
import org.apache.hbase.thirdparty.com.google.common.net.HostAndPort;
+import org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.ServletHolder;
/**
* Create a Jetty embedded server to answer http requests. The primary goal
@@ -128,6 +126,7 @@ public class InfoServer {
}
/**
+ * Adds a servlet in the server that any user can access.
* @see HttpServer#addUnprivilegedServlet(String, String, Class)
*/
public void addUnprivilegedServlet(String name, String pathSpec,
@@ -136,6 +135,18 @@ public class InfoServer {
}
/**
+ * Adds a servlet in the server that any user can access.
+ * @see HttpServer#addUnprivilegedServlet(String, ServletHolder)
+ */
+ public void addUnprivilegedServlet(String name, String pathSpec, ServletHolder holder) {
+ if (name != null) {
+ holder.setName(name);
+ }
+ this.httpServer.addUnprivilegedServlet(pathSpec, holder);
+ }
+
+ /**
+ * Adds a servlet in the server that any user can access.
* @see HttpServer#addPrivilegedServlet(String, String, Class)
*/
public void addPrivilegedServlet(String name, String pathSpec,
diff --git a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/gson/ByteArraySerializer.java b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/gson/ByteArraySerializer.java
new file mode 100644
index 0000000..fdcd347
--- /dev/null
+++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/gson/ByteArraySerializer.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.http.gson;
+
+import java.lang.reflect.Type;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.apache.hbase.thirdparty.com.google.gson.JsonElement;
+import org.apache.hbase.thirdparty.com.google.gson.JsonPrimitive;
+import org.apache.hbase.thirdparty.com.google.gson.JsonSerializationContext;
+import org.apache.hbase.thirdparty.com.google.gson.JsonSerializer;
+
+/**
+ * Serialize a {@code byte[]} using {@link Bytes#toString()}.
+ */
+@InterfaceAudience.Private
+public final class ByteArraySerializer implements JsonSerializer<byte[]> {
+
+ @Override
+ public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive(Bytes.toString(src));
+ }
+}
diff --git a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/gson/GsonMessageBodyWriter.java b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/gson/GsonMessageBodyWriter.java
new file mode 100644
index 0000000..c75113d
--- /dev/null
+++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/gson/GsonMessageBodyWriter.java
@@ -0,0 +1,99 @@
+/*
+ * 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.http.gson;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.Optional;
+import javax.inject.Inject;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.hbase.thirdparty.com.google.gson.Gson;
+import org.apache.hbase.thirdparty.javax.ws.rs.Produces;
+import org.apache.hbase.thirdparty.javax.ws.rs.WebApplicationException;
+import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType;
+import org.apache.hbase.thirdparty.javax.ws.rs.core.MultivaluedMap;
+import org.apache.hbase.thirdparty.javax.ws.rs.ext.MessageBodyWriter;
+
+/**
+ * Implements JSON serialization via {@link Gson} for JAX-RS.
+ */
+@InterfaceAudience.Private
+@Produces(MediaType.APPLICATION_JSON)
+public final class GsonMessageBodyWriter<T> implements MessageBodyWriter<T> {
+ private static final Logger logger = LoggerFactory.getLogger(GsonMessageBodyWriter.class);
+
+ private final Gson gson;
+
+ @Inject
+ public GsonMessageBodyWriter(Gson gson) {
+ this.gson = gson;
+ }
+
+ @Override
+ public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations,
+ MediaType mediaType) {
+ return mediaType == null || MediaType.APPLICATION_JSON_TYPE.isCompatible(mediaType);
+ }
+
+ @Override
+ public void writeTo(
+ T t,
+ Class<?> type,
+ Type genericType,
+ Annotation[] annotations,
+ MediaType mediaType,
+ MultivaluedMap<String, Object> httpHeaders,
+ OutputStream entityStream
+ ) throws IOException, WebApplicationException {
+ final Charset outputCharset = requestedCharset(mediaType);
+ try (Writer writer = new OutputStreamWriter(entityStream, outputCharset)) {
+ gson.toJson(t, writer);
+ }
+ }
+
+ private static Charset requestedCharset(MediaType mediaType) {
+ return Optional.ofNullable(mediaType)
+ .map(MediaType::getParameters)
+ .map(params -> params.get("charset"))
+ .map(c -> {
+ try {
+ return Charset.forName(c);
+ } catch (IllegalCharsetNameException e) {
+ logger.debug("Client requested illegal Charset '{}'", c);
+ return null;
+ } catch (UnsupportedCharsetException e) {
+ logger.debug("Client requested unsupported Charset '{}'", c);
+ return null;
+ } catch (Exception e) {
+ logger.debug("Error while resolving Charset '{}'", c, e);
+ return null;
+ }
+ })
+ .orElse(StandardCharsets.UTF_8);
+ }
+}
diff --git a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/jersey/ResponseEntityMapper.java b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/jersey/ResponseEntityMapper.java
new file mode 100644
index 0000000..dc3f8a7
--- /dev/null
+++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/jersey/ResponseEntityMapper.java
@@ -0,0 +1,53 @@
+/*
+ * 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.http.jersey;
+
+import java.io.IOException;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap;
+import org.apache.hbase.thirdparty.javax.ws.rs.container.ContainerRequestContext;
+import org.apache.hbase.thirdparty.javax.ws.rs.container.ContainerResponseContext;
+import org.apache.hbase.thirdparty.javax.ws.rs.container.ContainerResponseFilter;
+import org.apache.hbase.thirdparty.javax.ws.rs.core.Response.Status;
+
+/**
+ * Generate a uniform response wrapper around the Entity returned from the resource.
+ * @see <a href="https://jsonapi.org/format/#document-top-level">JSON API Document Structure</a>
+ * @see <a href="https://jsonapi.org/format/#error-objects">JSON API Error Objects</a>
+ */
+@InterfaceAudience.Private
+public class ResponseEntityMapper implements ContainerResponseFilter {
+
+ @Override
+ public void filter(
+ ContainerRequestContext requestContext,
+ ContainerResponseContext responseContext
+ ) throws IOException {
+ /*
+ * Follows very loosely the top-level document specification described in by JSON API. Only
+ * handles 200 response codes; leaves room for errors and other response types.
+ */
+
+ final int statusCode = responseContext.getStatus();
+ if (Status.OK.getStatusCode() != statusCode) {
+ return;
+ }
+
+ responseContext.setEntity(ImmutableMap.of("data", responseContext.getEntity()));
+ }
+}
diff --git a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/jersey/SupplierFactoryAdapter.java b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/jersey/SupplierFactoryAdapter.java
new file mode 100644
index 0000000..57a7e93
--- /dev/null
+++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/jersey/SupplierFactoryAdapter.java
@@ -0,0 +1,42 @@
+/*
+ * 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.http.jersey;
+
+import java.util.function.Supplier;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.apache.hbase.thirdparty.org.glassfish.hk2.api.Factory;
+
+/**
+ * Use a {@link Supplier} of type {@code T} as a {@link Factory} that provides instances of
+ * {@code T}. Modeled after Jersey's internal implementation.
+ */
+@InterfaceAudience.Private
+public class SupplierFactoryAdapter<T> implements Factory<T> {
+
+ private final Supplier<T> supplier;
+
+ public SupplierFactoryAdapter(Supplier<T> supplier) {
+ this.supplier = supplier;
+ }
+
+ @Override public T provide() {
+ return supplier.get();
+ }
+
+ @Override public void dispose(T instance) { }
+}
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 fb9c6d9..23dcfef 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
@@ -130,6 +130,7 @@ import org.apache.hadoop.hbase.master.cleaner.SnapshotCleanerChore;
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.janitor.CatalogJanitor;
import org.apache.hadoop.hbase.master.locking.LockManager;
import org.apache.hadoop.hbase.master.migrate.RollingUpgradeChore;
@@ -244,7 +245,8 @@ import org.apache.hbase.thirdparty.org.eclipse.jetty.server.Server;
import org.apache.hbase.thirdparty.org.eclipse.jetty.server.ServerConnector;
import org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.ServletHolder;
import org.apache.hbase.thirdparty.org.eclipse.jetty.webapp.WebAppContext;
-
+import org.apache.hbase.thirdparty.org.glassfish.jersey.server.ResourceConfig;
+import org.apache.hbase.thirdparty.org.glassfish.jersey.servlet.ServletContainer;
import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.GetRegionInfoResponse;
import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription;
@@ -268,7 +270,7 @@ public class HMaster extends HRegionServer implements MasterServices {
private static final Logger LOG = LoggerFactory.getLogger(HMaster.class);
// MASTER is name of the webapp and the attribute name used stuffing this
- //instance into web context.
+ // instance into a web context !! AND OTHER PLACES !!
public static final String MASTER = "master";
// Manager and zk listener for master election
@@ -673,12 +675,19 @@ public class HMaster extends HRegionServer implements MasterServices {
@Override
protected void configureInfoServer() {
infoServer.addUnprivilegedServlet("master-status", "/master-status", MasterStatusServlet.class);
+ infoServer.addUnprivilegedServlet("api_v1", "/api/v1/*", buildApiV1Servlet());
+
infoServer.setAttribute(MASTER, this);
if (LoadBalancer.isTablesOnMaster(conf)) {
super.configureInfoServer();
}
}
+ private ServletHolder buildApiV1Servlet() {
+ final ResourceConfig config = ResourceConfigFactory.createResourceConfig(conf, this);
+ return new ServletHolder(new ServletContainer(config));
+ }
+
@Override
protected Class<? extends HttpServlet> getDumpServlet() {
return MasterDumpServlet.class;
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/api_v1/ResourceConfigFactory.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/api_v1/ResourceConfigFactory.java
new file mode 100644
index 0000000..97860d8
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/api_v1/ResourceConfigFactory.java
@@ -0,0 +1,59 @@
+/*
+ * 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.api_v1;
+
+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;
+
+/**
+ * Encapsulates construction and configuration of the {@link ResourceConfig} that implements
+ * the {@code cluster-metrics} endpoints.
+ */
+@InterfaceAudience.Private
+public final class ResourceConfigFactory {
+
+ private ResourceConfigFactory() {}
+
+ public static ResourceConfig createResourceConfig(Configuration conf, HMaster master) {
+ return new ResourceConfig()
+ .setApplicationName("api_v1")
+ .packages(ResourceConfigFactory.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/api_v1/cluster_metrics/model/ClusterMetrics.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/api_v1/cluster_metrics/model/ClusterMetrics.java
new file mode 100644
index 0000000..5364194
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/api_v1/cluster_metrics/model/ClusterMetrics.java
@@ -0,0 +1,70 @@
+/*
+ * 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.api_v1.cluster_metrics.model;
+
+import java.util.List;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.yetus.audience.InterfaceAudience;
+
+/**
+ * Exposes a subset of fields from {@link org.apache.hadoop.hbase.ClusterMetrics}.
+ */
+@InterfaceAudience.Private
+public final class ClusterMetrics {
+
+ private final String hbaseVersion;
+ private final String clusterId;
+ private final ServerName masterName;
+ private final List<ServerName> backupMasterNames;
+
+ public static ClusterMetrics from(org.apache.hadoop.hbase.ClusterMetrics clusterMetrics) {
+ return new ClusterMetrics(
+ clusterMetrics.getHBaseVersion(),
+ clusterMetrics.getClusterId(),
+ clusterMetrics.getMasterName(),
+ clusterMetrics.getBackupMasterNames());
+ }
+
+ private ClusterMetrics(
+ String hbaseVersion,
+ String clusterId,
+ ServerName masterName,
+ List<ServerName> backupMasterNames
+ ) {
+ this.hbaseVersion = hbaseVersion;
+ this.clusterId = clusterId;
+ this.masterName = masterName;
+ this.backupMasterNames = backupMasterNames;
+ }
+
+ public String getHBaseVersion() {
+ return hbaseVersion;
+ }
+
+ public String getClusterId() {
+ return clusterId;
+ }
+
+ public ServerName getMasterName() {
+ return masterName;
+ }
+
+ public List<ServerName> getBackupMasterNames() {
+ return backupMasterNames;
+ }
+}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/api_v1/cluster_metrics/package-info.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/api_v1/cluster_metrics/package-info.java
new file mode 100644
index 0000000..5a470d1
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/api_v1/cluster_metrics/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+/**
+ * Exposes the {@link org.apache.hadoop.hbase.ClusterMetrics} object over HTTP as a REST
+ * resource hierarchy. Intended for Master
+ * {@link org.apache.hadoop.hbase.http.InfoServer} consumption only.
+ */
+@InterfaceAudience.Private
+package org.apache.hadoop.hbase.master.http.api_v1.cluster_metrics;
+
+import org.apache.yetus.audience.InterfaceAudience;
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/api_v1/cluster_metrics/resource/ClusterMetricsResource.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/api_v1/cluster_metrics/resource/ClusterMetricsResource.java
new file mode 100644
index 0000000..2fb4755
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/api_v1/cluster_metrics/resource/ClusterMetricsResource.java
@@ -0,0 +1,88 @@
+/*
+ * 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.api_v1.cluster_metrics.resource;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import javax.inject.Inject;
+import org.apache.hadoop.hbase.ClusterMetrics.Option;
+import org.apache.hadoop.hbase.ServerMetrics;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.client.Admin;
+import org.apache.hadoop.hbase.client.Connection;
+import org.apache.hadoop.hbase.master.MasterServices;
+import org.apache.hadoop.hbase.master.http.api_v1.cluster_metrics.model.ClusterMetrics;
+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 a subset of {@link org.apache.hadoop.hbase.ClusterMetrics}.
+ */
+@InterfaceAudience.Private
+@Path("cluster_metrics")
+@Produces({ MediaType.APPLICATION_JSON })
+public class ClusterMetricsResource {
+
+ // TODO: using the async client API lends itself well to using the JAX-RS 2.0 Spec's asynchronous
+ // server APIs. However, these are only available when Jersey is wired up using Servlet 3.x
+ // container and all of our existing InfoServer stuff is build on Servlet 2.x.
+ // See also https://blog.allegro.tech/2014/10/async-rest.html#mixing-with-completablefuture
+
+ final Connection connection;
+
+ @Inject
+ public ClusterMetricsResource(MasterServices master) throws IOException {
+ this.connection = master.getConnection();
+ }
+
+ private org.apache.hadoop.hbase.ClusterMetrics get(EnumSet<Option> fields)
+ throws IOException {
+ try (Admin admin = connection.getAdmin()) {
+ return admin.getClusterMetrics(fields);
+ }
+ }
+
+ @GET
+ @Path("/")
+ public ClusterMetrics getBaseMetrics() throws IOException {
+ final EnumSet<Option> fields = EnumSet.of(
+ Option.HBASE_VERSION,
+ Option.CLUSTER_ID,
+ Option.MASTER,
+ Option.BACKUP_MASTERS
+ );
+ return ClusterMetrics.from(get(fields));
+ }
+
+ @GET
+ @Path("/live_servers")
+ public Collection<ServerMetrics> getLiveServers() throws IOException {
+ return get(EnumSet.of(Option.LIVE_SERVERS)).getLiveServerMetrics().values();
+ }
+
+ @GET
+ @Path("/dead_servers")
+ public List<ServerName> getDeadServers() throws IOException {
+ return get(EnumSet.of(Option.DEAD_SERVERS)).getDeadServerNames();
+ }
+}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/gson/GsonFactory.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/gson/GsonFactory.java
new file mode 100644
index 0000000..ba56502
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/gson/GsonFactory.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.master.http.gson;
+
+import org.apache.hadoop.hbase.Size;
+import org.apache.hadoop.hbase.http.gson.ByteArraySerializer;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.apache.hbase.thirdparty.com.google.gson.FieldNamingPolicy;
+import org.apache.hbase.thirdparty.com.google.gson.Gson;
+import org.apache.hbase.thirdparty.com.google.gson.GsonBuilder;
+
+/**
+ * Provides a singleton {@link Gson} instance configured just the way we like it.
+ */
+@InterfaceAudience.Private
+public final class GsonFactory {
+
+ private GsonFactory() {}
+
+ public static Gson buildGson() {
+ return new GsonBuilder()
+ .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
+ .enableComplexMapKeySerialization()
+ .registerTypeAdapter(byte[].class, new ByteArraySerializer())
+ .registerTypeAdapter(Size.class, new SizeAsBytesSerializer())
+ .create();
+ }
+}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/gson/GsonSerializationFeature.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/gson/GsonSerializationFeature.java
new file mode 100644
index 0000000..344f78f
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/gson/GsonSerializationFeature.java
@@ -0,0 +1,67 @@
+/*
+ * 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.gson;
+
+import java.util.function.Supplier;
+import javax.inject.Singleton;
+import org.apache.hadoop.hbase.http.gson.GsonMessageBodyWriter;
+import org.apache.hadoop.hbase.http.jersey.SupplierFactoryAdapter;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.apache.hbase.thirdparty.com.google.gson.Gson;
+import org.apache.hbase.thirdparty.javax.ws.rs.core.Feature;
+import org.apache.hbase.thirdparty.javax.ws.rs.core.FeatureContext;
+import org.apache.hbase.thirdparty.javax.ws.rs.ext.MessageBodyWriter;
+import org.apache.hbase.thirdparty.org.glassfish.hk2.utilities.binding.AbstractBinder;
+import org.apache.hbase.thirdparty.org.glassfish.hk2.utilities.binding.ServiceBindingBuilder;
+
+/**
+ * Used to register with (shaded) Jersey the presence of Entity serialization using (shaded) Gson.
+ */
+@InterfaceAudience.Private
+public class GsonSerializationFeature implements Feature {
+
+ @Override
+ public boolean configure(FeatureContext context) {
+ context.register(new Binder());
+ return true;
+ }
+
+ /**
+ * Register this feature's provided functionality and defines their lifetime scopes.
+ */
+ private static class Binder extends AbstractBinder {
+
+ @Override
+ protected void configure() {
+ bindFactory(GsonFactory::buildGson)
+ .to(Gson.class)
+ .in(Singleton.class);
+ bind(GsonMessageBodyWriter.class)
+ .to(MessageBodyWriter.class)
+ .in(Singleton.class);
+ }
+
+ /**
+ * Helper method for smoothing over use of {@link SupplierFactoryAdapter}. Inspired by internal
+ * implementation details of jersey itself.
+ */
+ private <T> ServiceBindingBuilder<T> bindFactory(Supplier<T> supplier) {
+ return bindFactory(new SupplierFactoryAdapter<>(supplier));
+ }
+ }
+}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/gson/SizeAsBytesSerializer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/gson/SizeAsBytesSerializer.java
new file mode 100644
index 0000000..fcba115
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/gson/SizeAsBytesSerializer.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.master.http.gson;
+
+import java.lang.reflect.Type;
+import org.apache.hadoop.hbase.Size;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.apache.hbase.thirdparty.com.google.gson.JsonElement;
+import org.apache.hbase.thirdparty.com.google.gson.JsonPrimitive;
+import org.apache.hbase.thirdparty.com.google.gson.JsonSerializationContext;
+import org.apache.hbase.thirdparty.com.google.gson.JsonSerializer;
+
+/**
+ * Simplify representation of a {@link Size} instance by converting to bytes.
+ */
+@InterfaceAudience.Private
+final class SizeAsBytesSerializer implements JsonSerializer<Size> {
+
+ @Override
+ public JsonElement serialize(Size src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive(src.get(Size.Unit.BYTE));
+ }
+}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/gson/package-info.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/gson/package-info.java
new file mode 100644
index 0000000..22aac55
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/gson/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+/**
+ * This package should be in the hbase-http module as {@code a.a.h.h.http.gson}. It is here instead
+ * because hbase-http does not currently have a dependency on hbase-client, which is required for
+ * implementing {@link org.apache.hadoop.hbase.master.http.gson.SizeAsBytesSerializer}.
+ */
+package org.apache.hadoop.hbase.master.http.gson;
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/jersey/MasterFeature.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/jersey/MasterFeature.java
new file mode 100644
index 0000000..a530e2f
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/jersey/MasterFeature.java
@@ -0,0 +1,73 @@
+/*
+ * 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.jersey;
+
+import java.util.function.Supplier;
+import javax.inject.Singleton;
+import org.apache.hadoop.hbase.http.jersey.SupplierFactoryAdapter;
+import org.apache.hadoop.hbase.master.HMaster;
+import org.apache.hadoop.hbase.master.MasterServices;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.apache.hbase.thirdparty.javax.ws.rs.core.Feature;
+import org.apache.hbase.thirdparty.javax.ws.rs.core.FeatureContext;
+import org.apache.hbase.thirdparty.org.glassfish.hk2.utilities.binding.AbstractBinder;
+import org.apache.hbase.thirdparty.org.glassfish.hk2.utilities.binding.ServiceBindingBuilder;
+
+/**
+ * Implements a Singleton binding to the provided instance of {@link HMaster} for both
+ * {@link HMaster} and {@link MasterServices} injections.
+ */
+@InterfaceAudience.Private
+public class MasterFeature implements Feature {
+
+ private final Supplier<HMaster> supplier;
+
+ public MasterFeature(HMaster master) {
+ this.supplier = () -> master;
+ }
+
+ @Override
+ public boolean configure(FeatureContext context) {
+ context.register(new Binder());
+ return true;
+ }
+
+ /**
+ * Register this feature's provided functionality and defines their lifetime scopes.
+ */
+ private class Binder extends AbstractBinder {
+
+ @Override
+ protected void configure() {
+ bindFactory(supplier)
+ .to(HMaster.class)
+ .in(Singleton.class);
+ bindFactory(supplier)
+ .to(MasterServices.class)
+ .in(Singleton.class);
+ }
+
+ /**
+ * Helper method for smoothing over use of {@link SupplierFactoryAdapter}. Inspired by internal
+ * implementation details of jersey itself.
+ */
+ private <T> ServiceBindingBuilder<T> bindFactory(Supplier<T> supplier) {
+ return bindFactory(new SupplierFactoryAdapter<>(supplier));
+ }
+ }
+}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/MiniClusterRule.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/MiniClusterRule.java
index 4ee068f..6c31f4f 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/MiniClusterRule.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/MiniClusterRule.java
@@ -19,6 +19,7 @@ package org.apache.hadoop.hbase;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.client.AsyncConnection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
@@ -71,6 +72,10 @@ public final class MiniClusterRule extends ExternalResource {
return this;
}
+ public Builder setConfiguration(Supplier<Configuration> supplier) {
+ return setConfiguration(supplier.get());
+ }
+
public MiniClusterRule build() {
return new MiniClusterRule(
conf,
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/TestApiV1ClusterMetricsResource.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/TestApiV1ClusterMetricsResource.java
new file mode 100644
index 0000000..8f10546
--- /dev/null
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/TestApiV1ClusterMetricsResource.java
@@ -0,0 +1,203 @@
+/*
+ * 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.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 java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.ConnectionRule;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+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.TableDescriptor;
+import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
+import org.apache.hadoop.hbase.master.http.api_v1.cluster_metrics.resource.ClusterMetricsResource;
+import org.apache.hadoop.hbase.testclassification.LargeTests;
+import org.apache.hadoop.hbase.testclassification.MasterTests;
+import org.apache.hadoop.hbase.util.Bytes;
+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.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 master api_v1 {@link ClusterMetricsResource}.
+ */
+@Category({ MasterTests.class, LargeTests.class})
+public class TestApiV1ClusterMetricsResource {
+
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE =
+ HBaseClassTestRule.forClass(TestApiV1ClusterMetricsResource.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 = new Configuration();
+ conf.setInt(HConstants.MASTER_INFO_PORT, 0);
+ return conf;
+ })
+ .build();
+ private static final ConnectionRule connectionRule =
+ new ConnectionRule(miniClusterRule::createConnection);
+ private static final ClassSetup classRule = new ClassSetup(connectionRule::getConnection);
+
+ 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(TestApiV1ClusterMetricsResource.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();
+
+ 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("api/v1/cluster_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)
+ .get(String.class);
+ assertThat(response, allOf(
+ containsString("\"hbase_version\":"),
+ containsString("\"cluster_id\":"),
+ containsString("\"master_name\":"),
+ containsString("\"backup_master_names\":")));
+ }
+
+ @Test
+ public void testGetRootHtml() {
+ assertThrows(NotAcceptableException.class, () -> classRule.getTarget()
+ .request(MediaType.TEXT_HTML_TYPE)
+ .get(String.class));
+ }
+
+ @Test
+ public void testGetLiveServers() {
+ final String response = classRule.getTarget()
+ .path("live_servers")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .get(String.class);
+ assertThat(response, allOf(
+ startsWith("{\"data\":["),
+ endsWith("]}")));
+ }
+
+ @Test
+ public void testGetLiveServersHtml() {
+ assertThrows(NotAcceptableException.class, () -> classRule.getTarget()
+ .path("live_servers")
+ .request(MediaType.TEXT_HTML_TYPE)
+ .get(String.class));
+ }
+
+ @Test
+ public void testGetDeadServers() {
+ final String response = classRule.getTarget()
+ .path("dead_servers")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .get(String.class);
+ assertThat(response, allOf(
+ startsWith("{\"data\":["),
+ endsWith("]}")));
+ }
+
+ @Test
+ public void testGetDeadServersHtml() {
+ assertThrows(NotAcceptableException.class, () -> classRule.getTarget()
+ .path("dead_servers")
+ .request(MediaType.TEXT_HTML_TYPE)
+ .get(String.class));
+ }
+}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/gson/GsonFactoryTest.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/gson/GsonFactoryTest.java
new file mode 100644
index 0000000..3089f66
--- /dev/null
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/gson/GsonFactoryTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.gson;
+
+import static org.junit.Assert.assertEquals;
+import java.util.Map;
+import java.util.TreeMap;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.Size;
+import org.apache.hadoop.hbase.testclassification.MasterTests;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.apache.hbase.thirdparty.com.google.gson.Gson;
+
+@Category({ MasterTests.class, SmallTests.class})
+public class GsonFactoryTest {
+
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE =
+ HBaseClassTestRule.forClass(GsonFactoryTest.class);
+
+ private static Gson gson;
+
+ @BeforeClass
+ public static void beforeClass() {
+ gson = GsonFactory.buildGson();
+ }
+
+ @Test
+ public void testSerializeToLowerCaseUnderscores() {
+ final SomeBean input = new SomeBean(false, 57, "hello\n");
+ final String actual = gson.toJson(input);
+ final String expected = "{\"a_boolean\":false,\"an_int\":57,\"a_string\":\"hello\\n\"}";
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testSerializeMapWithSizeKeys() {
+ final Map<Size, String> input = new TreeMap<>();
+ input.put(new Size(10, Size.Unit.KILOBYTE), "10kb");
+ input.put(new Size(5, Size.Unit.MEGABYTE), "5mb");
+ final String actual = gson.toJson(input);
+ final String expected = "{\"10240.0\":\"10kb\",\"5242880.0\":\"5mb\"}";
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testSerializeNonPrintableByteArrays() {
+ final Map<byte[], byte[]> input = new TreeMap<>(Bytes.BYTES_COMPARATOR);
+ input.put(Bytes.toBytes("this is printable"), new byte[] { 0, 1, 2, 3, 4, 5 });
+ input.put(new byte[] { -127, -63, 0, 63, 127 }, Bytes.toBytes("test"));
+ final String actual = gson.toJson(input);
+ final String expected = "{" +
+ "\"this is printable\":\"\\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\"," +
+ "\"��\\u0000?\u007F\":\"test\"}";
+ assertEquals(expected, actual);
+ }
+
+ private static final class SomeBean {
+ private final boolean aBoolean;
+ private final int anInt;
+ private final String aString;
+
+ public SomeBean(
+ final boolean aBoolean,
+ final int anInt,
+ final String aString
+ ) {
+ this.aBoolean = aBoolean;
+ this.anInt = anInt;
+ this.aString = aString;
+ }
+
+ public boolean isaBoolean() {
+ return aBoolean;
+ }
+
+ public int getAnInt() {
+ return anInt;
+ }
+
+ public String getaString() {
+ return aString;
+ }
+ }
+}
[hbase] 02/02: HBASE-25895 Implement a Cluster Metrics JSON endpoint (addendum)
Posted by nd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
ndimiduk pushed a commit to branch branch-2
in repository https://gitbox.apache.org/repos/asf/hbase.git
commit e6879b8fd7d79923dbc5649bdef798af9b399562
Author: Nick Dimiduk <nd...@apache.org>
AuthorDate: Fri Mar 11 15:28:37 2022 +0100
HBASE-25895 Implement a Cluster Metrics JSON endpoint (addendum)
---
.../hbase/master/http/TestApiV1ClusterMetricsResource.java | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/TestApiV1ClusterMetricsResource.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/TestApiV1ClusterMetricsResource.java
index 8f10546..f89dd6c 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/TestApiV1ClusterMetricsResource.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/TestApiV1ClusterMetricsResource.java
@@ -28,6 +28,7 @@ import java.util.function.Supplier;
import org.apache.hadoop.conf.Configuration;
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;
@@ -72,8 +73,9 @@ public class TestApiV1ClusterMetricsResource {
.build())
.setConfiguration(() -> {
// enable Master InfoServer and random port selection
- final Configuration conf = new Configuration();
+ final Configuration conf = HBaseConfiguration.create();
conf.setInt(HConstants.MASTER_INFO_PORT, 0);
+ conf.set("hbase.http.jersey.tracing.type", "ON_DEMAND");
return conf;
})
.build();
@@ -148,6 +150,7 @@ public class TestApiV1ClusterMetricsResource {
public void testGetRoot() {
final String response = classRule.getTarget()
.request(MediaType.APPLICATION_JSON_TYPE)
+ .header("X-Jersey-Tracing-Accept", true)
.get(String.class);
assertThat(response, allOf(
containsString("\"hbase_version\":"),
@@ -160,6 +163,7 @@ public class TestApiV1ClusterMetricsResource {
public void testGetRootHtml() {
assertThrows(NotAcceptableException.class, () -> classRule.getTarget()
.request(MediaType.TEXT_HTML_TYPE)
+ .header("X-Jersey-Tracing-Accept", true)
.get(String.class));
}
@@ -168,6 +172,7 @@ public class TestApiV1ClusterMetricsResource {
final String response = classRule.getTarget()
.path("live_servers")
.request(MediaType.APPLICATION_JSON_TYPE)
+ .header("X-Jersey-Tracing-Accept", true)
.get(String.class);
assertThat(response, allOf(
startsWith("{\"data\":["),
@@ -179,6 +184,7 @@ public class TestApiV1ClusterMetricsResource {
assertThrows(NotAcceptableException.class, () -> classRule.getTarget()
.path("live_servers")
.request(MediaType.TEXT_HTML_TYPE)
+ .header("X-Jersey-Tracing-Accept", true)
.get(String.class));
}
@@ -187,6 +193,7 @@ public class TestApiV1ClusterMetricsResource {
final String response = classRule.getTarget()
.path("dead_servers")
.request(MediaType.APPLICATION_JSON_TYPE)
+ .header("X-Jersey-Tracing-Accept", true)
.get(String.class);
assertThat(response, allOf(
startsWith("{\"data\":["),
@@ -198,6 +205,7 @@ public class TestApiV1ClusterMetricsResource {
assertThrows(NotAcceptableException.class, () -> classRule.getTarget()
.path("dead_servers")
.request(MediaType.TEXT_HTML_TYPE)
+ .header("X-Jersey-Tracing-Accept", true)
.get(String.class));
}
}