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