From 5766792cad8ea9341ed08133872bdba6d2dd81c5 Mon Sep 17 00:00:00 2001 From: Anu Engineer Date: Wed, 3 Jan 2018 15:31:27 -0800 Subject: [PATCH] HDFS-12870. Ozone: Service Discovery: REST endpoint in KSM for getServiceList. Contributed by Nanda kumar. --- .../org/apache/hadoop/ozone/OzoneConsts.java | 5 + .../hadoop/ozone/ksm/helpers/ServiceInfo.java | 80 ++++++++++--- .../hadoop/ozone/ksm/KeySpaceManager.java | 7 +- .../ozone/ksm/KeySpaceManagerHttpServer.java | 6 +- .../ozone/ksm/ServiceListJSONServlet.java | 103 ++++++++++++++++ .../hadoop/ozone/web/OzoneHttpServer.java | 21 ++++ .../ksm/TestKeySpaceManagerHttpServer.java | 2 +- .../ksm/TestKeySpaceManagerRestInterface.java | 113 ++++++++++++++++++ 8 files changed, 317 insertions(+), 20 deletions(-) create mode 100644 hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/ksm/ServiceListJSONServlet.java create mode 100644 hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/ozone/ksm/TestKeySpaceManagerRestInterface.java diff --git a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java index fe8319c428..8ea7a23b1a 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java +++ b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java @@ -149,6 +149,11 @@ public enum Versioning {NOT_DEFINED, ENABLED, DISABLED} public static final int INVALID_PORT = -1; + + // The ServiceListJSONServlet context attribute where KeySpaceManager + // instance gets stored. + public static final String KSM_CONTEXT_ATTRIBUTE = "ozone.ksm"; + private OzoneConsts() { // Never Constructed } diff --git a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/ozone/ksm/helpers/ServiceInfo.java b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/ozone/ksm/helpers/ServiceInfo.java index 375e78c0ae..1c76b8a1ff 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/ozone/ksm/helpers/ServiceInfo.java +++ b/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/ozone/ksm/helpers/ServiceInfo.java @@ -19,12 +19,18 @@ package org.apache.hadoop.ozone.ksm.helpers; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.ObjectWriter; import com.google.common.base.Preconditions; +import org.apache.hadoop.ozone.client.rest.response.BucketInfo; import org.apache.hadoop.ozone.protocol.proto.KeySpaceManagerProtocolProtos; import org.apache.hadoop.ozone.protocol.proto.KeySpaceManagerProtocolProtos .ServicePort; import org.apache.hadoop.ozone.protocol.proto.OzoneProtos.NodeType; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -36,35 +42,45 @@ */ public final class ServiceInfo { + private static final ObjectReader READER = + new ObjectMapper().readerFor(ServiceInfo.class); + private static final ObjectWriter WRITER = + new ObjectMapper().writerWithDefaultPrettyPrinter(); + /** * Type of node/service. */ - private final NodeType nodeType; + private NodeType nodeType; /** * Hostname of the node in which the service is running. */ - private final String hostname; + private String hostname; /** * List of ports the service listens to. */ - private final Map portsMap; + private Map ports; + + /** + * Default constructor for JSON deserialization. + */ + public ServiceInfo() {} /** * Constructs the ServiceInfo for the {@code nodeType}. * @param nodeType type of node/service * @param hostname hostname of the service - * @param ports list of ports the service listens to + * @param portList list of ports the service listens to */ private ServiceInfo( - NodeType nodeType, String hostname, List ports) { + NodeType nodeType, String hostname, List portList) { Preconditions.checkNotNull(nodeType); Preconditions.checkNotNull(hostname); this.nodeType = nodeType; this.hostname = hostname; - this.portsMap = new HashMap<>(); - for (ServicePort port : ports) { - portsMap.put(port.getType(), port); + this.ports = new HashMap<>(); + for (ServicePort port : portList) { + ports.put(port.getType(), port.getValue()); } } @@ -85,11 +101,11 @@ public String getHostname() { } /** - * Returns the list of port which the service listens to. - * @return List + * Returns ServicePort.Type to port mappings. + * @return ports */ - public List getPorts() { - return portsMap.values().parallelStream().collect(Collectors.toList()); + public Map getPorts() { + return ports; } /** @@ -99,8 +115,9 @@ public List getPorts() { * @param type the type of port. * ex: RPC, HTTP, HTTPS, etc.. */ + @JsonIgnore public int getPort(ServicePort.Type type) { - return portsMap.get(type).getValue(); + return ports.get(type); } /** @@ -108,12 +125,20 @@ public int getPort(ServicePort.Type type) { * * @return KeySpaceManagerProtocolProtos.ServiceInfo */ + @JsonIgnore public KeySpaceManagerProtocolProtos.ServiceInfo getProtobuf() { KeySpaceManagerProtocolProtos.ServiceInfo.Builder builder = KeySpaceManagerProtocolProtos.ServiceInfo.newBuilder(); builder.setNodeType(nodeType) .setHostname(hostname) - .addAllServicePorts(portsMap.values()); + .addAllServicePorts( + ports.entrySet().stream() + .map( + entry -> + ServicePort.newBuilder() + .setType(entry.getKey()) + .setValue(entry.getValue()).build()) + .collect(Collectors.toList())); return builder.build(); } @@ -122,6 +147,7 @@ public KeySpaceManagerProtocolProtos.ServiceInfo getProtobuf() { * * @return {@link ServiceInfo} */ + @JsonIgnore public static ServiceInfo getFromProtobuf( KeySpaceManagerProtocolProtos.ServiceInfo serviceInfo) { return new ServiceInfo(serviceInfo.getNodeType(), @@ -129,6 +155,26 @@ public static ServiceInfo getFromProtobuf( serviceInfo.getServicePortsList()); } + /** + * Returns a JSON string of this object. + * + * @return String - json string + * @throws IOException + */ + public String toJsonString() throws IOException { + return WRITER.writeValueAsString(this); + } + + /** + * Parse a JSON string into ServiceInfo Object. + * + * @param jsonString Json String + * @return BucketInfo + * @throws IOException + */ + public static BucketInfo parse(String jsonString) throws IOException { + return READER.readValue(jsonString); + } /** * Creates a new builder to build {@link ServiceInfo}. @@ -145,7 +191,7 @@ public static class Builder { private NodeType node; private String host; - private List ports = new ArrayList<>(); + private List portList = new ArrayList<>(); /** @@ -174,7 +220,7 @@ public Builder setHostname(String hostname) { * @return the builder */ public Builder addServicePort(ServicePort servicePort) { - ports.add(servicePort); + portList.add(servicePort); return this; } @@ -184,7 +230,7 @@ public Builder addServicePort(ServicePort servicePort) { * @return {@link ServiceInfo} */ public ServiceInfo build() { - return new ServiceInfo(node, host, ports); + return new ServiceInfo(node, host, portList); } } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/ksm/KeySpaceManager.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/ksm/KeySpaceManager.java index 359dad76f1..cb960f5122 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/ksm/KeySpaceManager.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/ksm/KeySpaceManager.java @@ -173,7 +173,7 @@ private KeySpaceManager(OzoneConfiguration conf) throws IOException { metrics = KSMMetrics.create(); keyManager = new KeyManagerImpl(scmBlockClient, metadataManager, configuration); - httpServer = new KeySpaceManagerHttpServer(configuration); + httpServer = new KeySpaceManagerHttpServer(configuration, this); } /** @@ -785,6 +785,11 @@ public String getRpcPort() { return "" + ksmRpcAddress.getPort(); } + @VisibleForTesting + public KeySpaceManagerHttpServer getHttpServer() { + return httpServer; + } + @Override public List getServiceList() throws IOException { // When we implement multi-home this call has to be handled properly. diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/ksm/KeySpaceManagerHttpServer.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/ksm/KeySpaceManagerHttpServer.java index 395c782b82..b229c895c7 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/ksm/KeySpaceManagerHttpServer.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/ksm/KeySpaceManagerHttpServer.java @@ -19,6 +19,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.ozone.OzoneConfigKeys; +import org.apache.hadoop.ozone.OzoneConsts; import org.apache.hadoop.ozone.web.OzoneHttpServer; import java.io.IOException; @@ -28,8 +29,11 @@ */ public class KeySpaceManagerHttpServer extends OzoneHttpServer { - public KeySpaceManagerHttpServer(Configuration conf) throws IOException { + public KeySpaceManagerHttpServer(Configuration conf, KeySpaceManager ksm) + throws IOException { super(conf, "ksm"); + addServlet("serviceList", "/serviceList", ServiceListJSONServlet.class); + getWebAppContext().setAttribute(OzoneConsts.KSM_CONTEXT_ATTRIBUTE, ksm); } @Override protected String getHttpAddressKey() { diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/ksm/ServiceListJSONServlet.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/ksm/ServiceListJSONServlet.java new file mode 100644 index 0000000000..34a80ce104 --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/ksm/ServiceListJSONServlet.java @@ -0,0 +1,103 @@ +/** + * 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.ozone.ksm; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import org.apache.hadoop.ozone.OzoneConsts; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + + +/** + * Provides REST access to Ozone Service List. + *

+ * This servlet generally will be placed under the /serviceList URL of + * KeySpaceManager HttpServer. + * + * The return format is of JSON and in the form + *

+ *

+ *  {
+ *    "services" : [
+ *      {
+ *        "NodeType":"KSM",
+ *        "Hostname" "$hostname",
+ *        "ports" : {
+ *          "$PortType" : "$port",
+ *          ...
+ *        }
+ *      }
+ *    ]
+ *  }
+ *  
+ *

+ * + */ +public class ServiceListJSONServlet extends HttpServlet { + + private static final Logger LOG = + LoggerFactory.getLogger(ServiceListJSONServlet.class); + private static final long serialVersionUID = 1L; + + private KeySpaceManager ksm; + + public void init() throws ServletException { + this.ksm = (KeySpaceManager) getServletContext() + .getAttribute(OzoneConsts.KSM_CONTEXT_ATTRIBUTE); + } + + /** + * Process a GET request for the specified resource. + * + * @param request + * The servlet request we are processing + * @param response + * The servlet response we are creating + */ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.enable(SerializationFeature.INDENT_OUTPUT); + response.setContentType("application/json; charset=utf8"); + PrintWriter writer = response.getWriter(); + try { + writer.write(objectMapper.writeValueAsString(ksm.getServiceList())); + } finally { + if (writer != null) { + writer.close(); + } + } + } catch (IOException e) { + LOG.error( + "Caught an exception while processing ServiceList request", e); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } + +} diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/OzoneHttpServer.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/OzoneHttpServer.java index 74105aa339..b6a8fdaf30 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/OzoneHttpServer.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/OzoneHttpServer.java @@ -25,9 +25,11 @@ import org.apache.hadoop.http.HttpServer2; import org.apache.hadoop.net.NetUtils; import org.apache.hadoop.ozone.client.OzoneClientUtils; +import org.eclipse.jetty.webapp.WebAppContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.servlet.http.HttpServlet; import java.io.IOException; import java.net.InetSocketAddress; @@ -80,6 +82,25 @@ public OzoneHttpServer(Configuration conf, String name) throws IOException { } + /** + * Add a servlet to OzoneHttpServer. + * @param servletName The name of the servlet + * @param pathSpec The path spec for the servlet + * @param clazz The servlet class + */ + protected void addServlet(String servletName, String pathSpec, + Class clazz) { + httpServer.addServlet(servletName, pathSpec, clazz); + } + + /** + * Returns the WebAppContext associated with this HttpServer. + * @return WebAppContext + */ + protected WebAppContext getWebAppContext() { + return httpServer.getWebAppContext(); + } + protected InetSocketAddress getBindAddress(String bindHostKey, String addressKey, String bindHostDefault, int bindPortdefault) { final Optional bindHost = diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/ozone/ksm/TestKeySpaceManagerHttpServer.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/ozone/ksm/TestKeySpaceManagerHttpServer.java index d16d251b2a..c64f9e52d2 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/ozone/ksm/TestKeySpaceManagerHttpServer.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/ozone/ksm/TestKeySpaceManagerHttpServer.java @@ -99,7 +99,7 @@ public TestKeySpaceManagerHttpServer(Policy policy) { InetSocketAddress addr = InetSocketAddress.createUnresolved("localhost", 0); KeySpaceManagerHttpServer server = null; try { - server = new KeySpaceManagerHttpServer(conf); + server = new KeySpaceManagerHttpServer(conf, null); server.start(); Assert.assertTrue(implies(policy.isHttpEnabled(), diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/ozone/ksm/TestKeySpaceManagerRestInterface.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/ozone/ksm/TestKeySpaceManagerRestInterface.java new file mode 100644 index 0000000000..c719356956 --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/ozone/ksm/TestKeySpaceManagerRestInterface.java @@ -0,0 +1,113 @@ +/** + * 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.ozone.ksm; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.type.TypeReference; +import org.apache.hadoop.conf.OzoneConfiguration; +import org.apache.hadoop.net.NetUtils; +import org.apache.hadoop.ozone.MiniOzoneClassicCluster; +import org.apache.hadoop.ozone.MiniOzoneCluster; +import org.apache.hadoop.ozone.OzoneConsts; +import org.apache.hadoop.ozone.client.OzoneClientUtils; +import org.apache.hadoop.ozone.ksm.helpers.ServiceInfo; +import org.apache.hadoop.ozone.protocol.proto.KeySpaceManagerProtocolProtos; +import org.apache.hadoop.ozone.protocol.proto.OzoneProtos; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * This class is to test the REST interface exposed by KeySpaceManager. + */ +public class TestKeySpaceManagerRestInterface { + + private static MiniOzoneCluster cluster; + private static OzoneConfiguration conf; + + @BeforeClass + public static void setUp() throws Exception { + conf = new OzoneConfiguration(); + cluster = new MiniOzoneClassicCluster.Builder(conf) + .setHandlerType(OzoneConsts.OZONE_HANDLER_DISTRIBUTED) + .setClusterId(UUID.randomUUID().toString()) + .setScmId(UUID.randomUUID().toString()) + .build(); + } + + @AfterClass + public static void tearDown() throws Exception { + if (cluster != null) { + cluster.close(); + } + } + + @Test + public void testGetServiceList() throws Exception { + KeySpaceManagerHttpServer server = + cluster.getKeySpaceManager().getHttpServer(); + HttpClient client = HttpClients.createDefault(); + String connectionUri = "http://" + + NetUtils.getHostPortString(server.getHttpAddress()); + HttpGet httpGet = new HttpGet(connectionUri + "/serviceList"); + HttpResponse response = client.execute(httpGet); + String serviceListJson = EntityUtils.toString(response.getEntity()); + + ObjectMapper objectMapper = new ObjectMapper(); + TypeReference> serviceInfoReference = + new TypeReference>() {}; + List serviceInfos = objectMapper.readValue( + serviceListJson, serviceInfoReference); + Map serviceMap = new HashMap<>(); + for (ServiceInfo serviceInfo : serviceInfos) { + serviceMap.put(serviceInfo.getNodeType(), serviceInfo); + } + + InetSocketAddress ksmAddress = + OzoneClientUtils.getKsmAddressForClients(conf); + ServiceInfo ksmInfo = serviceMap.get(OzoneProtos.NodeType.KSM); + + Assert.assertEquals(ksmAddress.getHostName(), ksmInfo.getHostname()); + Assert.assertEquals(ksmAddress.getPort(), + ksmInfo.getPort(KeySpaceManagerProtocolProtos.ServicePort.Type.RPC)); + Assert.assertEquals(server.getHttpAddress().getPort(), + ksmInfo.getPort(KeySpaceManagerProtocolProtos.ServicePort.Type.HTTP)); + + InetSocketAddress scmAddress = + OzoneClientUtils.getScmAddressForClients(conf); + ServiceInfo scmInfo = serviceMap.get(OzoneProtos.NodeType.SCM); + + Assert.assertEquals(scmAddress.getHostName(), scmInfo.getHostname()); + Assert.assertEquals(scmAddress.getPort(), + scmInfo.getPort(KeySpaceManagerProtocolProtos.ServicePort.Type.RPC)); + } + +}