Skip to content

Commit bbf1250

Browse files
pbacskoaajisaka
authored andcommitted
YARN-10720. YARN WebAppProxyServlet should support connection timeout to prevent proxy server from hanging. Contributed by Qi Zhu.
(cherry picked from commit a0deda1) Conflicts: hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxyServlet.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestWebAppProxyServlet.java (cherry picked from commit dbeb41b) Conflicts: hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestWebAppProxyServlet.java
1 parent ce208b1 commit bbf1250

File tree

4 files changed

+136
-18
lines changed

4 files changed

+136
-18
lines changed

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1882,6 +1882,19 @@ public static boolean isAclEnabled(Configuration conf) {
18821882
public static final long DEFAULT_RM_APPLICATION_MONITOR_INTERVAL_MS =
18831883
3000;
18841884

1885+
// If the proxy connection time enabled.
1886+
public static final String RM_PROXY_TIMEOUT_ENABLED =
1887+
RM_PREFIX + "proxy.timeout.enabled";
1888+
1889+
public static final boolean DEFALUT_RM_PROXY_TIMEOUT_ENABLED =
1890+
true;
1891+
1892+
public static final String RM_PROXY_CONNECTION_TIMEOUT =
1893+
RM_PREFIX + "proxy.connection.timeout";
1894+
1895+
public static final int DEFAULT_RM_PROXY_CONNECTION_TIMEOUT =
1896+
60000;
1897+
18851898
/**
18861899
* Interval of time the linux container executor should try cleaning up
18871900
* 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
@@ -2024,6 +2024,18 @@
20242024
<value/>
20252025
</property>
20262026

2027+
<property>
2028+
<description>Enable the web proxy connection timeout, default is enabled.</description>
2029+
<name>yarn.resourcemanager.proxy.timeout.enabled</name>
2030+
<value>true</value>
2031+
</property>
2032+
2033+
<property>
2034+
<description>The web proxy connection timeout.</description>
2035+
<name>yarn.resourcemanager.proxy.connection.timeout</name>
2036+
<value>60000</value>
2037+
</property>
2038+
20272039
<!-- Applications' Configuration -->
20282040

20292041
<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: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,15 @@
6464
import org.apache.http.Header;
6565
import org.apache.http.HttpResponse;
6666
import org.apache.http.NameValuePair;
67+
import org.apache.http.client.HttpClient;
68+
import org.apache.http.client.config.RequestConfig;
6769
import org.apache.http.client.methods.HttpGet;
6870
import org.apache.http.client.methods.HttpPut;
6971
import org.apache.http.client.methods.HttpRequestBase;
70-
import org.apache.http.client.params.ClientPNames;
71-
import org.apache.http.client.params.CookiePolicy;
7272
import org.apache.http.client.utils.URLEncodedUtils;
73-
import org.apache.http.conn.params.ConnRoutePNames;
7473
import org.apache.http.entity.StringEntity;
75-
import org.apache.http.impl.client.DefaultHttpClient;
74+
import org.apache.http.impl.client.HttpClientBuilder;
75+
7676
import org.slf4j.Logger;
7777
import org.slf4j.LoggerFactory;
7878

@@ -123,6 +123,9 @@ public HTML<WebAppProxyServlet._> html() {
123123
}
124124
}
125125

126+
protected void setConf(YarnConfiguration conf){
127+
this.conf = conf;
128+
}
126129
/**
127130
* Default constructor
128131
*/
@@ -189,24 +192,38 @@ private static void warnUserPage(HttpServletResponse resp, String link,
189192
* @param method the http method
190193
* @throws IOException on any error.
191194
*/
192-
private static void proxyLink(final HttpServletRequest req,
195+
private void proxyLink(final HttpServletRequest req,
193196
final HttpServletResponse resp, final URI link, final Cookie c,
194197
final String proxyHost, final HTTP method) throws IOException {
195-
DefaultHttpClient client = new DefaultHttpClient();
196-
client
197-
.getParams()
198-
.setParameter(ClientPNames.COOKIE_POLICY,
199-
CookiePolicy.BROWSER_COMPATIBILITY)
200-
.setBooleanParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true);
198+
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
199+
200+
boolean connectionTimeoutEnabled =
201+
conf.getBoolean(YarnConfiguration.RM_PROXY_TIMEOUT_ENABLED,
202+
YarnConfiguration.DEFALUT_RM_PROXY_TIMEOUT_ENABLED);
203+
int connectionTimeout =
204+
conf.getInt(YarnConfiguration.RM_PROXY_CONNECTION_TIMEOUT,
205+
YarnConfiguration.DEFAULT_RM_PROXY_CONNECTION_TIMEOUT);
206+
201207
// Make sure we send the request from the proxy address in the config
202208
// since that is what the AM filter checks against. IP aliasing or
203209
// similar could cause issues otherwise.
204210
InetAddress localAddress = InetAddress.getByName(proxyHost);
205211
if (LOG.isDebugEnabled()) {
206212
LOG.debug("local InetAddress for proxy host: {}", localAddress);
207213
}
208-
client.getParams()
209-
.setParameter(ConnRoutePNames.LOCAL_ADDRESS, localAddress);
214+
httpClientBuilder.setDefaultRequestConfig(
215+
connectionTimeoutEnabled ?
216+
RequestConfig.custom()
217+
.setCircularRedirectsAllowed(true)
218+
.setLocalAddress(localAddress)
219+
.setConnectionRequestTimeout(connectionTimeout)
220+
.setSocketTimeout(connectionTimeout)
221+
.setConnectTimeout(connectionTimeout)
222+
.build() :
223+
RequestConfig.custom()
224+
.setCircularRedirectsAllowed(true)
225+
.setLocalAddress(localAddress)
226+
.build());
210227

211228
HttpRequestBase base = null;
212229
if (method.equals(HTTP.GET)) {
@@ -248,6 +265,7 @@ private static void proxyLink(final HttpServletRequest req,
248265
PROXY_USER_COOKIE_NAME + "=" + URLEncoder.encode(user, "ASCII"));
249266
}
250267
OutputStream out = resp.getOutputStream();
268+
HttpClient client = httpClientBuilder.build();
251269
try {
252270
HttpResponse httpResp = client.execute(base);
253271
resp.setStatus(httpResp.getStatusLine().getStatusCode());
@@ -571,7 +589,6 @@ private FetchedAppReport getFetchedAppReport(ApplicationId id)
571589
* again... If this method returns true, there was a redirect, and
572590
* it was handled by redirecting the current request to an error page.
573591
*
574-
* @param path the part of the request path after the app id
575592
* @param id the app id
576593
* @param req the request object
577594
* @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: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import static org.junit.Assert.assertFalse;
2323
import static org.junit.Assert.assertNotNull;
2424
import static org.junit.Assert.assertTrue;
25+
import static org.mockito.Mockito.mock;
26+
import static org.mockito.Mockito.when;
2527

2628
import java.io.ByteArrayOutputStream;
2729
import java.io.IOException;
@@ -32,10 +34,14 @@
3234
import java.net.HttpURLConnection;
3335
import java.net.URI;
3436
import java.net.URL;
37+
import java.net.SocketTimeoutException;
38+
import java.util.Collections;
3539
import java.util.Enumeration;
3640
import java.util.List;
3741
import java.util.Map;
3842

43+
import javax.servlet.ServletConfig;
44+
import javax.servlet.ServletContext;
3945
import javax.servlet.ServletException;
4046
import javax.servlet.http.HttpServlet;
4147
import javax.servlet.http.HttpServletRequest;
@@ -90,6 +96,7 @@ public static void start() throws Exception {
9096
context.setContextPath("/foo");
9197
server.setHandler(context);
9298
context.addServlet(new ServletHolder(TestServlet.class), "/bar");
99+
context.addServlet(new ServletHolder(TimeOutTestServlet.class), "/timeout");
93100
server.getConnectors()[0].setHost("localhost");
94101
server.start();
95102
originalPort = server.getConnectors()[0].getLocalPort();
@@ -137,6 +144,29 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp)
137144
}
138145
}
139146

147+
@SuppressWarnings("serial")
148+
public static class TimeOutTestServlet extends HttpServlet {
149+
150+
@Override
151+
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
152+
throws ServletException, IOException {
153+
try {
154+
Thread.sleep(10 * 1000);
155+
} catch (InterruptedException e) {
156+
LOG.warn("doGet() interrupted", e);
157+
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
158+
return;
159+
}
160+
resp.setStatus(HttpServletResponse.SC_OK);
161+
}
162+
163+
@Override
164+
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
165+
throws ServletException, IOException {
166+
resp.setStatus(HttpServletResponse.SC_OK);
167+
}
168+
}
169+
140170
@Test(timeout=5000)
141171
public void testWebAppProxyServlet() throws Exception {
142172
configuration.set(YarnConfiguration.PROXY_ADDRESS, "localhost:9090");
@@ -248,6 +278,45 @@ public void testWebAppProxyServlet() throws Exception {
248278
}
249279
}
250280

281+
@Test(expected = SocketTimeoutException.class)
282+
public void testWebAppProxyConnectionTimeout()
283+
throws IOException, ServletException{
284+
HttpServletRequest request = mock(HttpServletRequest.class);
285+
when(request.getMethod()).thenReturn("GET");
286+
when(request.getRemoteUser()).thenReturn("dr.who");
287+
when(request.getPathInfo()).thenReturn("/application_00_0");
288+
when(request.getHeaderNames()).thenReturn(Collections.emptyEnumeration());
289+
290+
HttpServletResponse response = mock(HttpServletResponse.class);
291+
when(response.getOutputStream()).thenReturn(null);
292+
293+
WebAppProxyServlet servlet = new WebAppProxyServlet();
294+
YarnConfiguration conf = new YarnConfiguration();
295+
conf.setBoolean(YarnConfiguration.RM_PROXY_TIMEOUT_ENABLED,
296+
true);
297+
conf.setInt(YarnConfiguration.RM_PROXY_CONNECTION_TIMEOUT,
298+
1000);
299+
300+
servlet.setConf(conf);
301+
302+
ServletConfig config = mock(ServletConfig.class);
303+
ServletContext context = mock(ServletContext.class);
304+
when(config.getServletContext()).thenReturn(context);
305+
306+
AppReportFetcherForTest appReportFetcher =
307+
new AppReportFetcherForTest(new YarnConfiguration());
308+
309+
when(config.getServletContext()
310+
.getAttribute(WebAppProxy.FETCHER_ATTRIBUTE))
311+
.thenReturn(appReportFetcher);
312+
313+
appReportFetcher.answer = 7;
314+
315+
servlet.init(config);
316+
servlet.doGet(request, response);
317+
318+
}
319+
251320
@Test(timeout=5000)
252321
public void testAppReportForEmptyTrackingUrl() throws Exception {
253322
configuration.set(YarnConfiguration.PROXY_ADDRESS, "localhost:9090");
@@ -330,12 +399,13 @@ public void testWebAppProxyPassThroughHeaders() throws Exception {
330399
assertEquals(proxyConn.getRequestProperties().size(), 4);
331400
proxyConn.connect();
332401
assertEquals(HttpURLConnection.HTTP_OK, proxyConn.getResponseCode());
333-
// Verify if number of headers received by end server is 8.
334-
// Eight headers include Accept, Host, Connection, User-Agent, Cookie,
335-
// Origin, Access-Control-Request-Method and
402+
// Verify if number of headers received by end server is 9.
403+
// This should match WebAppProxyServlet#PASS_THROUGH_HEADERS.
404+
// Nine headers include Accept, Host, Connection, User-Agent, Cookie,
405+
// Origin, Access-Control-Request-Method, Accept-Encoding, and
336406
// Access-Control-Request-Headers. Pls note that Unknown-Header is dropped
337407
// by proxy as it is not in the list of allowed headers.
338-
assertEquals(numberOfHeaders, 8);
408+
assertEquals(numberOfHeaders, 9);
339409
assertFalse(hasUnknownHeader);
340410
} finally {
341411
proxy.close();
@@ -553,6 +623,12 @@ public FetchedAppReport getApplicationReport(ApplicationId appId)
553623
return result;
554624
} else if (answer == 6) {
555625
return getDefaultApplicationReport(appId, false);
626+
} else if (answer == 7) {
627+
// test connection timeout
628+
FetchedAppReport result = getDefaultApplicationReport(appId);
629+
result.getApplicationReport().setOriginalTrackingUrl("localhost:"
630+
+ originalPort + "/foo/timeout?a=b#main");
631+
return result;
556632
}
557633
return null;
558634
}

0 commit comments

Comments
 (0)