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 <busbey@apache.org> Signed-off-by: Andrew Purtell <apurtell@apache.org>
This commit is contained in:
parent
50e12309ea
commit
cdabd6a1ad
|
@ -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;
|
||||
|
@ -838,6 +839,17 @@ public class HttpServer implements FilterContainer {
|
|||
addServletWithAuth(name, pathSpec, clazz, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -848,6 +860,16 @@ public class HttpServer implements FilterContainer {
|
|||
addServletWithAuth(name, pathSpec, clazz, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -859,6 +881,16 @@ public class HttpServer implements FilterContainer {
|
|||
addFilterPathMapping(pathSpec, webAppContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
@ -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());
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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) { }
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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\"," +
|
||||
"\"<EFBFBD><EFBFBD>\\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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue