Skip to content

Commit be0afbf

Browse files
committed
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 <[email protected]> Signed-off-by: Andrew Purtell <[email protected]>
1 parent 5851400 commit be0afbf

File tree

19 files changed

+1112
-11
lines changed

19 files changed

+1112
-11
lines changed

hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import javax.servlet.Filter;
4040
import javax.servlet.FilterChain;
4141
import javax.servlet.FilterConfig;
42+
import javax.servlet.Servlet;
4243
import javax.servlet.ServletContext;
4344
import javax.servlet.ServletException;
4445
import javax.servlet.ServletRequest;
@@ -838,6 +839,17 @@ public void addUnprivilegedServlet(String name, String pathSpec,
838839
addServletWithAuth(name, pathSpec, clazz, false);
839840
}
840841

842+
/**
843+
* Adds a servlet in the server that any user can access. This method differs from
844+
* {@link #addPrivilegedServlet(String, ServletHolder)} in that any authenticated user
845+
* can interact with the servlet added by this method.
846+
* @param pathSpec The path spec for the servlet
847+
* @param holder The servlet holder
848+
*/
849+
public void addUnprivilegedServlet(String pathSpec, ServletHolder holder) {
850+
addServletWithAuth(pathSpec, holder, false);
851+
}
852+
841853
/**
842854
* Adds a servlet in the server that only administrators can access. This method differs from
843855
* {@link #addUnprivilegedServlet(String, String, Class)} in that only those authenticated user
@@ -848,6 +860,16 @@ public void addPrivilegedServlet(String name, String pathSpec,
848860
addServletWithAuth(name, pathSpec, clazz, true);
849861
}
850862

863+
/**
864+
* Adds a servlet in the server that only administrators can access. This method differs from
865+
* {@link #addUnprivilegedServlet(String, ServletHolder)} in that only those
866+
* authenticated user who are identified as administrators can interact with the servlet added by
867+
* this method.
868+
*/
869+
public void addPrivilegedServlet(String pathSpec, ServletHolder holder) {
870+
addServletWithAuth(pathSpec, holder, true);
871+
}
872+
851873
/**
852874
* Internal method to add a servlet to the HTTP server. Developers should not call this method
853875
* directly, but invoke it via {@link #addUnprivilegedServlet(String, String, Class)} or
@@ -859,6 +881,16 @@ void addServletWithAuth(String name, String pathSpec,
859881
addFilterPathMapping(pathSpec, webAppContext);
860882
}
861883

884+
/**
885+
* Internal method to add a servlet to the HTTP server. Developers should not call this method
886+
* directly, but invoke it via {@link #addUnprivilegedServlet(String, ServletHolder)} or
887+
* {@link #addPrivilegedServlet(String, ServletHolder)}.
888+
*/
889+
void addServletWithAuth(String pathSpec, ServletHolder holder, boolean requireAuthz) {
890+
addInternalServlet(pathSpec, holder, requireAuthz);
891+
addFilterPathMapping(pathSpec, webAppContext);
892+
}
893+
862894
/**
863895
* Add an internal servlet in the server, specifying whether or not to
864896
* protect with Kerberos authentication.
@@ -867,17 +899,33 @@ void addServletWithAuth(String name, String pathSpec,
867899
* servlets added using this method, filters (except internal Kerberos
868900
* filters) are not enabled.
869901
*
870-
* @param name The name of the servlet (can be passed as null)
871-
* @param pathSpec The path spec for the servlet
872-
* @param clazz The servlet class
873-
* @param requireAuth Require Kerberos authenticate to access servlet
902+
* @param name The name of the {@link Servlet} (can be passed as null)
903+
* @param pathSpec The path spec for the {@link Servlet}
904+
* @param clazz The {@link Servlet} class
905+
* @param requireAuthz Require Kerberos authenticate to access servlet
874906
*/
875907
void addInternalServlet(String name, String pathSpec,
876-
Class<? extends HttpServlet> clazz, boolean requireAuthz) {
908+
Class<? extends HttpServlet> clazz, boolean requireAuthz) {
877909
ServletHolder holder = new ServletHolder(clazz);
878910
if (name != null) {
879911
holder.setName(name);
880912
}
913+
addInternalServlet(pathSpec, holder, requireAuthz);
914+
}
915+
916+
/**
917+
* Add an internal servlet in the server, specifying whether or not to
918+
* protect with Kerberos authentication.
919+
* Note: This method is to be used for adding servlets that facilitate
920+
* internal communication and not for user facing functionality. For
921+
* servlets added using this method, filters (except internal Kerberos
922+
* filters) are not enabled.
923+
*
924+
* @param pathSpec The path spec for the {@link Servlet}
925+
* @param holder The object providing the {@link Servlet} instance
926+
* @param requireAuthz Require Kerberos authenticate to access servlet
927+
*/
928+
void addInternalServlet(String pathSpec, ServletHolder holder, boolean requireAuthz) {
881929
if (authenticationEnabled && requireAuthz) {
882930
FilterHolder filter = new FilterHolder(AdminAuthorizedFilter.class);
883931
filter.setName(AdminAuthorizedFilter.class.getSimpleName());

hbase-http/src/main/java/org/apache/hadoop/hbase/http/InfoServer.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/**
1+
/*
22
* Licensed to the Apache Software Foundation (ASF) under one
33
* or more contributor license agreements. See the NOTICE file
44
* distributed with this work for additional information
@@ -19,18 +19,16 @@
1919

2020
import java.io.IOException;
2121
import java.net.URI;
22-
2322
import javax.servlet.ServletContext;
2423
import javax.servlet.http.HttpServlet;
2524
import javax.servlet.http.HttpServletRequest;
26-
2725
import org.apache.hadoop.conf.Configuration;
2826
import org.apache.hadoop.fs.CommonConfigurationKeys;
2927
import org.apache.hadoop.hbase.HBaseConfiguration;
3028
import org.apache.hadoop.security.authorize.AccessControlList;
3129
import org.apache.yetus.audience.InterfaceAudience;
32-
3330
import org.apache.hbase.thirdparty.com.google.common.net.HostAndPort;
31+
import org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.ServletHolder;
3432

3533
/**
3634
* Create a Jetty embedded server to answer http requests. The primary goal
@@ -128,6 +126,7 @@ public void addServlet(String name, String pathSpec,
128126
}
129127

130128
/**
129+
* Adds a servlet in the server that any user can access.
131130
* @see HttpServer#addUnprivilegedServlet(String, String, Class)
132131
*/
133132
public void addUnprivilegedServlet(String name, String pathSpec,
@@ -136,6 +135,18 @@ public void addUnprivilegedServlet(String name, String pathSpec,
136135
}
137136

138137
/**
138+
* Adds a servlet in the server that any user can access.
139+
* @see HttpServer#addUnprivilegedServlet(String, ServletHolder)
140+
*/
141+
public void addUnprivilegedServlet(String name, String pathSpec, ServletHolder holder) {
142+
if (name != null) {
143+
holder.setName(name);
144+
}
145+
this.httpServer.addUnprivilegedServlet(pathSpec, holder);
146+
}
147+
148+
/**
149+
* Adds a servlet in the server that any user can access.
139150
* @see HttpServer#addPrivilegedServlet(String, String, Class)
140151
*/
141152
public void addPrivilegedServlet(String name, String pathSpec,
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.http.gson;
19+
20+
import java.lang.reflect.Type;
21+
import org.apache.hadoop.hbase.util.Bytes;
22+
import org.apache.yetus.audience.InterfaceAudience;
23+
import org.apache.hbase.thirdparty.com.google.gson.JsonElement;
24+
import org.apache.hbase.thirdparty.com.google.gson.JsonPrimitive;
25+
import org.apache.hbase.thirdparty.com.google.gson.JsonSerializationContext;
26+
import org.apache.hbase.thirdparty.com.google.gson.JsonSerializer;
27+
28+
/**
29+
* Serialize a {@code byte[]} using {@link Bytes#toString()}.
30+
*/
31+
@InterfaceAudience.Private
32+
public final class ByteArraySerializer implements JsonSerializer<byte[]> {
33+
34+
@Override
35+
public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
36+
return new JsonPrimitive(Bytes.toString(src));
37+
}
38+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.http.gson;
19+
20+
import java.io.IOException;
21+
import java.io.OutputStream;
22+
import java.io.OutputStreamWriter;
23+
import java.io.Writer;
24+
import java.lang.annotation.Annotation;
25+
import java.lang.reflect.Type;
26+
import java.nio.charset.Charset;
27+
import java.nio.charset.IllegalCharsetNameException;
28+
import java.nio.charset.StandardCharsets;
29+
import java.nio.charset.UnsupportedCharsetException;
30+
import java.util.Optional;
31+
import javax.inject.Inject;
32+
import org.apache.yetus.audience.InterfaceAudience;
33+
import org.slf4j.Logger;
34+
import org.slf4j.LoggerFactory;
35+
import org.apache.hbase.thirdparty.com.google.gson.Gson;
36+
import org.apache.hbase.thirdparty.javax.ws.rs.Produces;
37+
import org.apache.hbase.thirdparty.javax.ws.rs.WebApplicationException;
38+
import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType;
39+
import org.apache.hbase.thirdparty.javax.ws.rs.core.MultivaluedMap;
40+
import org.apache.hbase.thirdparty.javax.ws.rs.ext.MessageBodyWriter;
41+
42+
/**
43+
* Implements JSON serialization via {@link Gson} for JAX-RS.
44+
*/
45+
@InterfaceAudience.Private
46+
@Produces(MediaType.APPLICATION_JSON)
47+
public final class GsonMessageBodyWriter<T> implements MessageBodyWriter<T> {
48+
private static final Logger logger = LoggerFactory.getLogger(GsonMessageBodyWriter.class);
49+
50+
private final Gson gson;
51+
52+
@Inject
53+
public GsonMessageBodyWriter(Gson gson) {
54+
this.gson = gson;
55+
}
56+
57+
@Override
58+
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations,
59+
MediaType mediaType) {
60+
return mediaType == null || MediaType.APPLICATION_JSON_TYPE.isCompatible(mediaType);
61+
}
62+
63+
@Override
64+
public void writeTo(
65+
T t,
66+
Class<?> type,
67+
Type genericType,
68+
Annotation[] annotations,
69+
MediaType mediaType,
70+
MultivaluedMap<String, Object> httpHeaders,
71+
OutputStream entityStream
72+
) throws IOException, WebApplicationException {
73+
final Charset outputCharset = requestedCharset(mediaType);
74+
try (Writer writer = new OutputStreamWriter(entityStream, outputCharset)) {
75+
gson.toJson(t, writer);
76+
}
77+
}
78+
79+
private static Charset requestedCharset(MediaType mediaType) {
80+
return Optional.ofNullable(mediaType)
81+
.map(MediaType::getParameters)
82+
.map(params -> params.get("charset"))
83+
.map(c -> {
84+
try {
85+
return Charset.forName(c);
86+
} catch (IllegalCharsetNameException e) {
87+
logger.debug("Client requested illegal Charset '{}'", c);
88+
return null;
89+
} catch (UnsupportedCharsetException e) {
90+
logger.debug("Client requested unsupported Charset '{}'", c);
91+
return null;
92+
} catch (Exception e) {
93+
logger.debug("Error while resolving Charset '{}'", c, e);
94+
return null;
95+
}
96+
})
97+
.orElse(StandardCharsets.UTF_8);
98+
}
99+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.http.jersey;
19+
20+
import java.io.IOException;
21+
import org.apache.yetus.audience.InterfaceAudience;
22+
import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap;
23+
import org.apache.hbase.thirdparty.javax.ws.rs.container.ContainerRequestContext;
24+
import org.apache.hbase.thirdparty.javax.ws.rs.container.ContainerResponseContext;
25+
import org.apache.hbase.thirdparty.javax.ws.rs.container.ContainerResponseFilter;
26+
import org.apache.hbase.thirdparty.javax.ws.rs.core.Response.Status;
27+
28+
/**
29+
* Generate a uniform response wrapper around the Entity returned from the resource.
30+
* @see <a href="https://jsonapi.org/format/#document-top-level">JSON API Document Structure</a>
31+
* @see <a href="https://jsonapi.org/format/#error-objects">JSON API Error Objects</a>
32+
*/
33+
@InterfaceAudience.Private
34+
public class ResponseEntityMapper implements ContainerResponseFilter {
35+
36+
@Override
37+
public void filter(
38+
ContainerRequestContext requestContext,
39+
ContainerResponseContext responseContext
40+
) throws IOException {
41+
/*
42+
* Follows very loosely the top-level document specification described in by JSON API. Only
43+
* handles 200 response codes; leaves room for errors and other response types.
44+
*/
45+
46+
final int statusCode = responseContext.getStatus();
47+
if (Status.OK.getStatusCode() != statusCode) {
48+
return;
49+
}
50+
51+
responseContext.setEntity(ImmutableMap.of("data", responseContext.getEntity()));
52+
}
53+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.http.jersey;
19+
20+
import java.util.function.Supplier;
21+
import org.apache.yetus.audience.InterfaceAudience;
22+
import org.apache.hbase.thirdparty.org.glassfish.hk2.api.Factory;
23+
24+
/**
25+
* Use a {@link Supplier} of type {@code T} as a {@link Factory} that provides instances of
26+
* {@code T}. Modeled after Jersey's internal implementation.
27+
*/
28+
@InterfaceAudience.Private
29+
public class SupplierFactoryAdapter<T> implements Factory<T> {
30+
31+
private final Supplier<T> supplier;
32+
33+
public SupplierFactoryAdapter(Supplier<T> supplier) {
34+
this.supplier = supplier;
35+
}
36+
37+
@Override public T provide() {
38+
return supplier.get();
39+
}
40+
41+
@Override public void dispose(T instance) { }
42+
}

0 commit comments

Comments
 (0)