Skip to content

Commit a0deda1

Browse files
committed
YARN-10720. YARN WebAppProxyServlet should support connection timeout to prevent proxy server from hanging. Contributed by Qi Zhu.
1 parent 0665ce9 commit a0deda1

File tree

4 files changed

+126
-7
lines changed

4 files changed

+126
-7
lines changed

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2668,6 +2668,20 @@ public static boolean isAclEnabled(Configuration conf) {
26682668

26692669
public static final String DEFAULT_RM_APPLICATION_HTTPS_POLICY = "NONE";
26702670

2671+
2672+
// If the proxy connection time enabled.
2673+
public static final String RM_PROXY_TIMEOUT_ENABLED =
2674+
RM_PREFIX + "proxy.timeout.enabled";
2675+
2676+
public static final boolean DEFALUT_RM_PROXY_TIMEOUT_ENABLED =
2677+
true;
2678+
2679+
public static final String RM_PROXY_CONNECTION_TIMEOUT =
2680+
RM_PREFIX + "proxy.connection.timeout";
2681+
2682+
public static final int DEFAULT_RM_PROXY_CONNECTION_TIMEOUT =
2683+
60000;
2684+
26712685
/**
26722686
* Interval of time the linux container executor should try cleaning up
26732687
* cgroups entry when cleaning up a container. This is required due to what

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2574,6 +2574,18 @@
25742574
<value/>
25752575
</property>
25762576

2577+
<property>
2578+
<description>Enable the web proxy connection timeout, default is enabled.</description>
2579+
<name>yarn.resourcemanager.proxy.timeout.enabled</name>
2580+
<value>true</value>
2581+
</property>
2582+
2583+
<property>
2584+
<description>The web proxy connection timeout.</description>
2585+
<name>yarn.resourcemanager.proxy.connection.timeout</name>
2586+
<value>60000</value>
2587+
</property>
2588+
25772589
<!-- Applications' Configuration -->
25782590

25792591
<property>

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxyServlet.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ public HTML<WebAppProxyServlet.__> html() {
122122
}
123123
}
124124

125+
protected void setConf(YarnConfiguration conf){
126+
this.conf = conf;
127+
}
125128
/**
126129
* Default constructor
127130
*/
@@ -230,6 +233,14 @@ private void proxyLink(final HttpServletRequest req,
230233

231234
String httpsPolicy = conf.get(YarnConfiguration.RM_APPLICATION_HTTPS_POLICY,
232235
YarnConfiguration.DEFAULT_RM_APPLICATION_HTTPS_POLICY);
236+
237+
boolean connectionTimeoutEnabled =
238+
conf.getBoolean(YarnConfiguration.RM_PROXY_TIMEOUT_ENABLED,
239+
YarnConfiguration.DEFALUT_RM_PROXY_TIMEOUT_ENABLED);
240+
int connectionTimeout =
241+
conf.getInt(YarnConfiguration.RM_PROXY_CONNECTION_TIMEOUT,
242+
YarnConfiguration.DEFAULT_RM_PROXY_CONNECTION_TIMEOUT);
243+
233244
if (httpsPolicy.equals("LENIENT") || httpsPolicy.equals("STRICT")) {
234245
ProxyCA proxyCA = getProxyCA();
235246
// ProxyCA could be null when the Proxy is run outside the RM
@@ -250,10 +261,18 @@ private void proxyLink(final HttpServletRequest req,
250261
InetAddress localAddress = InetAddress.getByName(proxyHost);
251262
LOG.debug("local InetAddress for proxy host: {}", localAddress);
252263
httpClientBuilder.setDefaultRequestConfig(
253-
RequestConfig.custom()
254-
.setCircularRedirectsAllowed(true)
255-
.setLocalAddress(localAddress)
256-
.build());
264+
connectionTimeoutEnabled ?
265+
RequestConfig.custom()
266+
.setCircularRedirectsAllowed(true)
267+
.setLocalAddress(localAddress)
268+
.setConnectionRequestTimeout(connectionTimeout)
269+
.setSocketTimeout(connectionTimeout)
270+
.setConnectTimeout(connectionTimeout)
271+
.build() :
272+
RequestConfig.custom()
273+
.setCircularRedirectsAllowed(true)
274+
.setLocalAddress(localAddress)
275+
.build());
257276

258277
HttpRequestBase base = null;
259278
if (method.equals(HTTP.GET)) {
@@ -621,7 +640,6 @@ private FetchedAppReport getFetchedAppReport(ApplicationId id)
621640
* again... If this method returns true, there was a redirect, and
622641
* it was handled by redirecting the current request to an error page.
623642
*
624-
* @param path the part of the request path after the app id
625643
* @param id the app id
626644
* @param req the request object
627645
* @param resp the response object

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestWebAppProxyServlet.java

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import static org.junit.Assert.assertFalse;
2424
import static org.junit.Assert.assertNotNull;
2525
import static org.junit.Assert.assertTrue;
26+
import static org.mockito.Mockito.mock;
27+
import static org.mockito.Mockito.when;
2628

2729
import java.io.ByteArrayOutputStream;
2830
import java.io.IOException;
@@ -35,10 +37,14 @@
3537
import java.net.HttpURLConnection;
3638
import java.net.URI;
3739
import java.net.URL;
40+
import java.net.SocketTimeoutException;
41+
import java.util.Collections;
3842
import java.util.Enumeration;
3943
import java.util.List;
4044
import java.util.Map;
4145

46+
import javax.servlet.ServletConfig;
47+
import javax.servlet.ServletContext;
4248
import javax.servlet.ServletException;
4349
import javax.servlet.http.HttpServlet;
4450
import javax.servlet.http.HttpServletRequest;
@@ -98,6 +104,7 @@ public static void start() throws Exception {
98104
context.setContextPath("/foo");
99105
server.setHandler(context);
100106
context.addServlet(new ServletHolder(TestServlet.class), "/bar");
107+
context.addServlet(new ServletHolder(TimeOutTestServlet.class), "/timeout");
101108
((ServerConnector)server.getConnectors()[0]).setHost("localhost");
102109
server.start();
103110
originalPort = ((ServerConnector)server.getConnectors()[0]).getLocalPort();
@@ -145,6 +152,29 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp)
145152
}
146153
}
147154

155+
@SuppressWarnings("serial")
156+
public static class TimeOutTestServlet extends HttpServlet {
157+
158+
@Override
159+
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
160+
throws ServletException, IOException {
161+
try {
162+
Thread.sleep(10 * 1000);
163+
} catch (InterruptedException e) {
164+
LOG.warn("doGet() interrupted", e);
165+
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
166+
return;
167+
}
168+
resp.setStatus(HttpServletResponse.SC_OK);
169+
}
170+
171+
@Override
172+
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
173+
throws ServletException, IOException {
174+
resp.setStatus(HttpServletResponse.SC_OK);
175+
}
176+
}
177+
148178
@Test(timeout=5000)
149179
public void testWebAppProxyServlet() throws Exception {
150180
configuration.set(YarnConfiguration.PROXY_ADDRESS, "localhost:9090");
@@ -256,6 +286,45 @@ public void testWebAppProxyServlet() throws Exception {
256286
}
257287
}
258288

289+
@Test(expected = SocketTimeoutException.class)
290+
public void testWebAppProxyConnectionTimeout()
291+
throws IOException, ServletException{
292+
HttpServletRequest request = mock(HttpServletRequest.class);
293+
when(request.getMethod()).thenReturn("GET");
294+
when(request.getRemoteUser()).thenReturn("dr.who");
295+
when(request.getPathInfo()).thenReturn("/application_00_0");
296+
when(request.getHeaderNames()).thenReturn(Collections.emptyEnumeration());
297+
298+
HttpServletResponse response = mock(HttpServletResponse.class);
299+
when(response.getOutputStream()).thenReturn(null);
300+
301+
WebAppProxyServlet servlet = new WebAppProxyServlet();
302+
YarnConfiguration conf = new YarnConfiguration();
303+
conf.setBoolean(YarnConfiguration.RM_PROXY_TIMEOUT_ENABLED,
304+
true);
305+
conf.setInt(YarnConfiguration.RM_PROXY_CONNECTION_TIMEOUT,
306+
1000);
307+
308+
servlet.setConf(conf);
309+
310+
ServletConfig config = mock(ServletConfig.class);
311+
ServletContext context = mock(ServletContext.class);
312+
when(config.getServletContext()).thenReturn(context);
313+
314+
AppReportFetcherForTest appReportFetcher =
315+
new AppReportFetcherForTest(new YarnConfiguration());
316+
317+
when(config.getServletContext()
318+
.getAttribute(WebAppProxy.FETCHER_ATTRIBUTE))
319+
.thenReturn(appReportFetcher);
320+
321+
appReportFetcher.answer = 7;
322+
323+
servlet.init(config);
324+
servlet.doGet(request, response);
325+
326+
}
327+
259328
@Test(timeout=5000)
260329
public void testAppReportForEmptyTrackingUrl() throws Exception {
261330
configuration.set(YarnConfiguration.PROXY_ADDRESS, "localhost:9090");
@@ -391,9 +460,9 @@ public void testWebAppProxyServerMainMethod() throws Exception {
391460

392461
@Test(timeout=5000)
393462
public void testCheckHttpsStrictAndNotProvided() throws Exception {
394-
HttpServletResponse resp = Mockito.mock(HttpServletResponse.class);
463+
HttpServletResponse resp = mock(HttpServletResponse.class);
395464
StringWriter sw = new StringWriter();
396-
Mockito.when(resp.getWriter()).thenReturn(new PrintWriter(sw));
465+
when(resp.getWriter()).thenReturn(new PrintWriter(sw));
397466
YarnConfiguration conf = new YarnConfiguration();
398467
final URI httpLink = new URI("http://foo.com");
399468
final URI httpsLink = new URI("https://foo.com");
@@ -566,6 +635,12 @@ public FetchedAppReport getApplicationReport(ApplicationId appId)
566635
return result;
567636
} else if (answer == 6) {
568637
return getDefaultApplicationReport(appId, false);
638+
} else if (answer == 7) {
639+
// test connection timeout
640+
FetchedAppReport result = getDefaultApplicationReport(appId);
641+
result.getApplicationReport().setOriginalTrackingUrl("localhost:"
642+
+ originalPort + "/foo/timeout?a=b#main");
643+
return result;
569644
}
570645
return null;
571646
}

0 commit comments

Comments
 (0)