diff --git a/hadoop-ozone/integration-test-s3/pom.xml b/hadoop-ozone/integration-test-s3/pom.xml index 2cf6ae0d8bd5..1c41eee0d6d0 100644 --- a/hadoop-ozone/integration-test-s3/pom.xml +++ b/hadoop-ozone/integration-test-s3/pom.xml @@ -36,11 +36,26 @@ aws-java-sdk-s3 test + + com.google.guava + guava + test + + + commons-io + commons-io + test + jakarta.xml.bind jakarta.xml.bind-api test + + javax.servlet + javax.servlet-api + test + org.apache.commons commons-lang3 @@ -97,6 +112,31 @@ ratis-common test + + org.eclipse.jetty + jetty-client + test + + + org.eclipse.jetty + jetty-proxy + test + + + org.eclipse.jetty + jetty-server + test + + + org.eclipse.jetty + jetty-servlet + test + + + org.eclipse.jetty + jetty-util + test + org.slf4j slf4j-api diff --git a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/LoadBalanceStrategy.java b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/LoadBalanceStrategy.java new file mode 100644 index 000000000000..10c4a95dd9a1 --- /dev/null +++ b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/LoadBalanceStrategy.java @@ -0,0 +1,34 @@ +/* + * 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.s3; + +import java.util.List; + +/** + * Interface for load balancing strategies to select endpoints. + */ +public interface LoadBalanceStrategy { + + /** + * Select the next endpoint from the available endpoints list. + * + * @param endpoints list of available endpoints + * @return the selected endpoint URL + */ + String selectEndpoint(List endpoints); +} diff --git a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/MultiS3GatewayService.java b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/MultiS3GatewayService.java new file mode 100644 index 000000000000..82bd2831b167 --- /dev/null +++ b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/MultiS3GatewayService.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.ozone.s3; + +import static org.apache.ozone.test.GenericTestUtils.PortAllocator.localhostWithFreePort; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.ozone.MiniOzoneCluster; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Multi S3 Gateway for {@link MiniOzoneCluster}. + */ +public class MultiS3GatewayService implements MiniOzoneCluster.Service { + + private static final Logger LOG = LoggerFactory.getLogger(MultiS3GatewayService.class); + + private final List gatewayServices = new ArrayList<>(); + private ProxyServer proxyServer; + private OzoneConfiguration configuration; + + public MultiS3GatewayService(int numGateways) { + for (int i = 0; i < numGateways; i++) { + gatewayServices.add(new S3GatewayService()); + } + } + + @Override + public void start(OzoneConfiguration conf) throws Exception { + List urls = new ArrayList<>(); + for (S3GatewayService service : gatewayServices) { + service.start(conf); + String redirectUrl = "http://" + service.getConf().get(S3GatewayConfigKeys.OZONE_S3G_HTTP_ADDRESS_KEY); + urls.add(redirectUrl); + } + + configuration = new OzoneConfiguration(conf); + configuration.set(S3GatewayConfigKeys.OZONE_S3G_HTTP_ADDRESS_KEY, localhostWithFreePort()); + String url = configuration.get(S3GatewayConfigKeys.OZONE_S3G_HTTP_ADDRESS_KEY); + URI proxyUri = new URI("http://" + url); + proxyServer = new ProxyServer(urls, proxyUri.getHost(), proxyUri.getPort()); + proxyServer.start(); + } + + @Override + public void stop() throws Exception { + Exception exception = null; + + if (proxyServer != null) { + try { + proxyServer.stop(); + } catch (Exception e) { + LOG.warn("Error stopping proxy server", e); + exception = e; + } + } + + for (S3GatewayService service : gatewayServices) { + try { + service.stop(); + } catch (Exception e) { + LOG.warn("Error stopping S3 gateway service", e); + if (exception == null) { + exception = e; + } else { + exception.addSuppressed(e); + } + } + } + + if (exception != null) { + throw exception; + } + } + + public OzoneConfiguration getConf() { + return configuration; + } + +} diff --git a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/ProxyServer.java b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/ProxyServer.java new file mode 100644 index 000000000000..d43e9f566bc2 --- /dev/null +++ b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/ProxyServer.java @@ -0,0 +1,215 @@ +/* + * 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.s3; + +import com.google.common.net.HttpHeaders; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.proxy.ProxyServlet; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * ProxyServer acts as a reverse proxy for S3G endpoints. + * It forwards requests to the selected S3G endpoint based on the load balancing strategy. + */ +public class ProxyServer { + private static final Logger LOG = LoggerFactory.getLogger(ProxyServer.class); + private final List s3gEndpoints; + private final LoadBalanceStrategy loadBalanceStrategy; + private final Server server; + private final String host; + private final int port; + + public ProxyServer(List s3gEndpoints, String host, int proxyPort) throws Exception { + this(s3gEndpoints, host, proxyPort, new RoundRobinStrategy()); + } + + public ProxyServer(List s3gEndpoints, String host, int proxyPort, + LoadBalanceStrategy loadBalanceStrategy) throws Exception { + this.s3gEndpoints = s3gEndpoints; + this.loadBalanceStrategy = loadBalanceStrategy; + this.host = host; + this.port = proxyPort; + + server = new Server(proxyPort); + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + + ProxyHandler proxyHandler = new ProxyHandler(); + ServletHolder proxyHolder = new ServletHolder(proxyHandler); + + proxyHolder.setInitParameter("proxyTo", ""); + + + context.addServlet(proxyHolder, "/*"); + server.setHandler(context); + + LOG.info("ProxyServer initialized with endpoints: {}", s3gEndpoints); + LOG.info("Load balance strategy: {}", loadBalanceStrategy.getClass().getSimpleName()); + } + + public void start() throws Exception { + server.start(); + LOG.info("Proxy started on http://{}:{}", host, port); + } + + public void stop() throws Exception { + server.stop(); + LOG.info("Proxy stopped on http://{}:{}", host, port); + } + + public boolean isStarted() { + return server.isStarted(); + } + + /** + * ProxyHandler is a subclass of Jetty's ProxyServlet.Transparent. + * It implements logic for request rewriting, service handling, + * proxy response failure, and rewrite failure handling. + * This handler is mainly used to forward requests to different S3G endpoints + * based on the load balancing strategy, handle special HTTP headers (such as Expect), + * and manage exceptions during the proxy process. + */ + public class ProxyHandler extends ProxyServlet.Transparent { + + @Override + public void init() throws ServletException { + super.init(); + + LOG.info("MyProxyHandler initialized with {} endpoints", + s3gEndpoints != null ? s3gEndpoints.size() : 0); + if (s3gEndpoints != null) { + LOG.info("Endpoints: {}", s3gEndpoints); + } + LOG.info("Load balance strategy: {}", loadBalanceStrategy.getClass().getSimpleName()); + } + + @Override + protected String rewriteTarget(HttpServletRequest request) { + + String baseUrl = loadBalanceStrategy.selectEndpoint(s3gEndpoints); + + String requestUri = request.getRequestURI(); + String queryString = request.getQueryString(); + + StringBuilder targetUrl = new StringBuilder(baseUrl); + if (requestUri != null) { + targetUrl.append(requestUri); + } + if (queryString != null) { + targetUrl.append('?').append(queryString); + } + + String finalUrl = targetUrl.toString(); + LOG.info("Rewriting target URL: [{}] {}", request.getMethod(), finalUrl); + return finalUrl; + } + + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + // Remove the Expect header to avoid Jetty's 100-continue handling issue: + // In some scenarios (e.g. testPutObjectEmpty), when content-length != 0, Jetty triggers the 100-continue process, + // causing the server to wait for a 100 response code. However, Jetty only sends the 100 response + // when the InputStream is read, which can lead to request failures. + if (request.getHeader(HttpHeaders.EXPECT) != null) { + LOG.info("Removing Expect header: {}", request.getHeader(HttpHeaders.EXPECT)); + + request = new HttpServletRequestWrapper(request) { + @Override + public String getHeader(String name) { + if ("Expect".equalsIgnoreCase(name)) { + return null; + } + return super.getHeader(name); + } + + @Override + public Enumeration getHeaders(String name) { + if ("Expect".equalsIgnoreCase(name)) { + return Collections.emptyEnumeration(); + } + return super.getHeaders(name); + } + + @Override + public Enumeration getHeaderNames() { + List headerNames = new ArrayList<>(); + Enumeration originalHeaders = super.getHeaderNames(); + while (originalHeaders.hasMoreElements()) { + String headerName = originalHeaders.nextElement(); + if (!"Expect".equalsIgnoreCase(headerName)) { + headerNames.add(headerName); + } + } + return Collections.enumeration(headerNames); + } + }; + } + + super.service(request, response); + } + + @Override + protected void onProxyResponseFailure(HttpServletRequest clientRequest, + HttpServletResponse proxyResponse, + org.eclipse.jetty.client.api.Response serverResponse, + Throwable failure) { + LOG.error("=== Proxy Response Failure==="); + LOG.error("Client request: {} {}", clientRequest.getMethod(), clientRequest.getRequestURL()); + if (serverResponse != null) { + LOG.error("Server response status: {}", serverResponse.getStatus()); + } else { + LOG.error("Server response is null - connection failed"); + } + + LOG.error("Failure details:", failure); + super.onProxyResponseFailure(clientRequest, proxyResponse, serverResponse, failure); + } + + @Override + protected void onProxyRewriteFailed(HttpServletRequest clientRequest, + HttpServletResponse proxyResponse) { + LOG.error("=== Proxy Rewrite Failed ==="); + LOG.error("Client request: {} {}", clientRequest.getMethod(), clientRequest.getRequestURL()); + LOG.error("Rewrite target returned null or invalid URL"); + + try { + proxyResponse.setStatus(HttpServletResponse.SC_BAD_GATEWAY); + proxyResponse.setContentType("text/plain"); + proxyResponse.getWriter().write("Proxy configuration error: Unable to rewrite target URL"); + } catch (IOException e) { + LOG.error("Failed to send rewrite error response", e); + } + + super.onProxyRewriteFailed(clientRequest, proxyResponse); + } + } +} diff --git a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/ProxyServerIntegrationTest.java b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/ProxyServerIntegrationTest.java new file mode 100644 index 000000000000..1e5fcacf6429 --- /dev/null +++ b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/ProxyServerIntegrationTest.java @@ -0,0 +1,163 @@ +/* + * 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.s3; + +import static org.apache.ozone.test.GenericTestUtils.PortAllocator.localhostWithFreePort; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; +import java.util.concurrent.TimeoutException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.ozone.test.GenericTestUtils; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * Integration test class to verify the routing and direct access functionality + * of the ProxyServer. + */ +public class ProxyServerIntegrationTest { + private static final int NUM_SERVERS = 3; + private static final String SERVICE_PATH = "/service-name"; + + private static final List SERVERS = new ArrayList<>(); + private static final List ENDPOINTS = new ArrayList<>(); + private static ProxyServer proxy; + private static String proxyUrl; + + @BeforeAll + public static void setup() throws Exception { + for (int i = 0; i < NUM_SERVERS; i++) { + startMockServer("server-" + i); + } + waitForMockServersStarted(); + startProxy(); + waitForProxyReady(); + } + + @AfterAll + public static void cleanup() throws Exception { + if (proxy != null) { + proxy.stop(); + } + for (Server server : SERVERS) { + server.stop(); + } + } + + private static void startMockServer(String name) throws Exception { + String address = localhostWithFreePort(); + int port = Integer.parseInt(address.split(":")[1]); + + Server server = new Server(port); + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + context.addServlet(new ServletHolder(new ServiceServlet(name)), SERVICE_PATH); + server.setHandler(context); + server.start(); + + SERVERS.add(server); + ENDPOINTS.add("http://" + address); + } + + private static void startProxy() throws Exception { + String address = localhostWithFreePort(); + String[] hostPort = address.split(":"); + proxy = new ProxyServer(ENDPOINTS, hostPort[0], Integer.parseInt(hostPort[1])); + proxy.start(); + proxyUrl = "http://" + address + SERVICE_PATH; + } + + private static void waitForMockServersStarted() throws TimeoutException, InterruptedException { + for (Server server : SERVERS) { + GenericTestUtils.waitFor(server::isStarted, 100, 3000); + } + } + + private static void waitForProxyReady() throws TimeoutException, InterruptedException { + GenericTestUtils.waitFor(() -> { + try { + return proxy.isStarted(); + } catch (Exception e) { + return false; + } + }, 50, 3000); + } + + @Test + public void testRouting() throws IOException { + for (int i = 0; i < NUM_SERVERS * 2; i++) { + String response = sendRequest(proxyUrl); + assertThat(response).isEqualTo("server-" + i % NUM_SERVERS); + } + } + + @Test + public void testDirectAccess() throws IOException { + for (int i = 0; i < ENDPOINTS.size(); i++) { + String response = sendRequest(ENDPOINTS.get(i) + SERVICE_PATH); + assertThat(response).isEqualTo("server-" + i); + } + } + + private static String sendRequest(String url) throws IOException { + HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(5000); + conn.setReadTimeout(5000); + + if (conn.getResponseCode() != 200) { + throw new IOException("HTTP " + conn.getResponseCode() + " from " + url); + } + + try (InputStream is = conn.getInputStream(); + Scanner scanner = new Scanner(is, StandardCharsets.UTF_8.name())) { + return scanner.useDelimiter("\\A").hasNext() ? scanner.next() : ""; + } + } + + /** + * A simple servlet that returns the name of the server. + */ + public static class ServiceServlet extends HttpServlet { + private final String name; + + public ServiceServlet(String name) { + this.name = name; + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + resp.setContentType("text/plain"); + resp.getWriter().write(name); + } + } +} diff --git a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/RoundRobinStrategy.java b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/RoundRobinStrategy.java new file mode 100644 index 000000000000..e1869c4cad40 --- /dev/null +++ b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/RoundRobinStrategy.java @@ -0,0 +1,41 @@ +/* + * 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.s3; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Round Robin load balancing strategy. + * Distributes requests evenly across all available endpoints in a circular manner. + */ +public class RoundRobinStrategy implements LoadBalanceStrategy { + + private final AtomicInteger counter = new AtomicInteger(0); + + @Override + public String selectEndpoint(List endpoints) { + if (endpoints == null || endpoints.isEmpty()) { + throw new IllegalArgumentException("Endpoints list cannot be null or empty"); + } + + int idx = counter.getAndUpdate(i -> (i + 1) % endpoints.size()); + return endpoints.get(idx); + } + +} diff --git a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java index f4e75fe92843..47b64eaf7698 100644 --- a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java +++ b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java @@ -110,8 +110,8 @@ import org.apache.hadoop.ozone.om.helpers.BucketLayout; import org.apache.hadoop.ozone.om.helpers.OmBucketInfo; import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol; +import org.apache.hadoop.ozone.s3.MultiS3GatewayService; import org.apache.hadoop.ozone.s3.S3ClientFactory; -import org.apache.hadoop.ozone.s3.S3GatewayService; import org.apache.hadoop.ozone.s3.endpoint.S3Owner; import org.apache.hadoop.security.UserGroupInformation; import org.apache.ozone.test.OzoneTestBase; @@ -194,7 +194,7 @@ public abstract class AbstractS3SDKV1Tests extends OzoneTestBase { * @throws Exception exception thrown when waiting for the cluster to be ready. */ static void startCluster(OzoneConfiguration conf) throws Exception { - S3GatewayService s3g = new S3GatewayService(); + MultiS3GatewayService s3g = new MultiS3GatewayService(5); cluster = MiniOzoneCluster.newBuilder(conf) .addService(s3g) .setNumDatanodes(5) diff --git a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java index 6417ed3a4ed7..d46c469c55cc 100644 --- a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java +++ b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java @@ -63,8 +63,8 @@ import org.apache.hadoop.ozone.client.OzoneClient; import org.apache.hadoop.ozone.client.OzoneVolume; import org.apache.hadoop.ozone.client.io.OzoneOutputStream; +import org.apache.hadoop.ozone.s3.MultiS3GatewayService; import org.apache.hadoop.ozone.s3.S3ClientFactory; -import org.apache.hadoop.ozone.s3.S3GatewayService; import org.apache.hadoop.ozone.s3.endpoint.S3Owner; import org.apache.hadoop.security.UserGroupInformation; import org.apache.ozone.test.OzoneTestBase; @@ -162,7 +162,7 @@ public abstract class AbstractS3SDKV2Tests extends OzoneTestBase { * @throws Exception exception thrown when waiting for the cluster to be ready. */ static void startCluster(OzoneConfiguration conf) throws Exception { - S3GatewayService s3g = new S3GatewayService(); + MultiS3GatewayService s3g = new MultiS3GatewayService(5); cluster = MiniOzoneCluster.newBuilder(conf) .addService(s3g) .setNumDatanodes(5) diff --git a/pom.xml b/pom.xml index 857c3579b168..d5f70c08470c 100644 --- a/pom.xml +++ b/pom.xml @@ -1390,6 +1390,11 @@ jetty-io ${jetty.version} + + org.eclipse.jetty + jetty-proxy + ${jetty.version} + org.eclipse.jetty jetty-server