diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java index a2b4f3ddac9ed..88498a7f5d65d 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java @@ -1882,6 +1882,19 @@ public static boolean isAclEnabled(Configuration conf) { public static final long DEFAULT_RM_APPLICATION_MONITOR_INTERVAL_MS = 3000; + // If the proxy connection time enabled. + public static final String RM_PROXY_TIMEOUT_ENABLED = + RM_PREFIX + "proxy.timeout.enabled"; + + public static final boolean DEFALUT_RM_PROXY_TIMEOUT_ENABLED = + true; + + public static final String RM_PROXY_CONNECTION_TIMEOUT = + RM_PREFIX + "proxy.connection.timeout"; + + public static final int DEFAULT_RM_PROXY_CONNECTION_TIMEOUT = + 60000; + /** * Interval of time the linux container executor should try cleaning up * cgroups entry when cleaning up a container. This is required due to what diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml index 6b964fed5308e..f87114c273405 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml @@ -2024,6 +2024,18 @@ + + Enable the web proxy connection timeout, default is enabled. + yarn.resourcemanager.proxy.timeout.enabled + true + + + + The web proxy connection timeout. + yarn.resourcemanager.proxy.connection.timeout + 60000 + + diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxyServlet.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxyServlet.java index 65281ba11d0e0..7d5baf8386e9f 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxyServlet.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxyServlet.java @@ -64,15 +64,15 @@ import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.client.params.ClientPNames; -import org.apache.http.client.params.CookiePolicy; import org.apache.http.client.utils.URLEncodedUtils; -import org.apache.http.conn.params.ConnRoutePNames; import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -123,6 +123,9 @@ public HTML html() { } } + protected void setConf(YarnConfiguration conf){ + this.conf = conf; + } /** * Default constructor */ @@ -189,15 +192,18 @@ private static void warnUserPage(HttpServletResponse resp, String link, * @param method the http method * @throws IOException on any error. */ - private static void proxyLink(final HttpServletRequest req, + private void proxyLink(final HttpServletRequest req, final HttpServletResponse resp, final URI link, final Cookie c, final String proxyHost, final HTTP method) throws IOException { - DefaultHttpClient client = new DefaultHttpClient(); - client - .getParams() - .setParameter(ClientPNames.COOKIE_POLICY, - CookiePolicy.BROWSER_COMPATIBILITY) - .setBooleanParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true); + HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); + + boolean connectionTimeoutEnabled = + conf.getBoolean(YarnConfiguration.RM_PROXY_TIMEOUT_ENABLED, + YarnConfiguration.DEFALUT_RM_PROXY_TIMEOUT_ENABLED); + int connectionTimeout = + conf.getInt(YarnConfiguration.RM_PROXY_CONNECTION_TIMEOUT, + YarnConfiguration.DEFAULT_RM_PROXY_CONNECTION_TIMEOUT); + // Make sure we send the request from the proxy address in the config // since that is what the AM filter checks against. IP aliasing or // similar could cause issues otherwise. @@ -205,8 +211,19 @@ private static void proxyLink(final HttpServletRequest req, if (LOG.isDebugEnabled()) { LOG.debug("local InetAddress for proxy host: {}", localAddress); } - client.getParams() - .setParameter(ConnRoutePNames.LOCAL_ADDRESS, localAddress); + httpClientBuilder.setDefaultRequestConfig( + connectionTimeoutEnabled ? + RequestConfig.custom() + .setCircularRedirectsAllowed(true) + .setLocalAddress(localAddress) + .setConnectionRequestTimeout(connectionTimeout) + .setSocketTimeout(connectionTimeout) + .setConnectTimeout(connectionTimeout) + .build() : + RequestConfig.custom() + .setCircularRedirectsAllowed(true) + .setLocalAddress(localAddress) + .build()); HttpRequestBase base = null; if (method.equals(HTTP.GET)) { @@ -248,6 +265,7 @@ private static void proxyLink(final HttpServletRequest req, PROXY_USER_COOKIE_NAME + "=" + URLEncoder.encode(user, "ASCII")); } OutputStream out = resp.getOutputStream(); + HttpClient client = httpClientBuilder.build(); try { HttpResponse httpResp = client.execute(base); resp.setStatus(httpResp.getStatusLine().getStatusCode()); @@ -571,7 +589,6 @@ private FetchedAppReport getFetchedAppReport(ApplicationId id) * again... If this method returns true, there was a redirect, and * it was handled by redirecting the current request to an error page. * - * @param path the part of the request path after the app id * @param id the app id * @param req the request object * @param resp the response object diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestWebAppProxyServlet.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestWebAppProxyServlet.java index 990b6ddcf5fc9..4ccd24e483d8a 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestWebAppProxyServlet.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestWebAppProxyServlet.java @@ -22,6 +22,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -32,10 +34,14 @@ import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; +import java.net.SocketTimeoutException; +import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.Map; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -90,6 +96,7 @@ public static void start() throws Exception { context.setContextPath("/foo"); server.setHandler(context); context.addServlet(new ServletHolder(TestServlet.class), "/bar"); + context.addServlet(new ServletHolder(TimeOutTestServlet.class), "/timeout"); server.getConnectors()[0].setHost("localhost"); server.start(); originalPort = server.getConnectors()[0].getLocalPort(); @@ -137,6 +144,29 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) } } + @SuppressWarnings("serial") + public static class TimeOutTestServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + try { + Thread.sleep(10 * 1000); + } catch (InterruptedException e) { + LOG.warn("doGet() interrupted", e); + resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + resp.setStatus(HttpServletResponse.SC_OK); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setStatus(HttpServletResponse.SC_OK); + } + } + @Test(timeout=5000) public void testWebAppProxyServlet() throws Exception { configuration.set(YarnConfiguration.PROXY_ADDRESS, "localhost:9090"); @@ -248,6 +278,45 @@ public void testWebAppProxyServlet() throws Exception { } } + @Test(expected = SocketTimeoutException.class) + public void testWebAppProxyConnectionTimeout() + throws IOException, ServletException{ + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getMethod()).thenReturn("GET"); + when(request.getRemoteUser()).thenReturn("dr.who"); + when(request.getPathInfo()).thenReturn("/application_00_0"); + when(request.getHeaderNames()).thenReturn(Collections.emptyEnumeration()); + + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.getOutputStream()).thenReturn(null); + + WebAppProxyServlet servlet = new WebAppProxyServlet(); + YarnConfiguration conf = new YarnConfiguration(); + conf.setBoolean(YarnConfiguration.RM_PROXY_TIMEOUT_ENABLED, + true); + conf.setInt(YarnConfiguration.RM_PROXY_CONNECTION_TIMEOUT, + 1000); + + servlet.setConf(conf); + + ServletConfig config = mock(ServletConfig.class); + ServletContext context = mock(ServletContext.class); + when(config.getServletContext()).thenReturn(context); + + AppReportFetcherForTest appReportFetcher = + new AppReportFetcherForTest(new YarnConfiguration()); + + when(config.getServletContext() + .getAttribute(WebAppProxy.FETCHER_ATTRIBUTE)) + .thenReturn(appReportFetcher); + + appReportFetcher.answer = 7; + + servlet.init(config); + servlet.doGet(request, response); + + } + @Test(timeout=5000) public void testAppReportForEmptyTrackingUrl() throws Exception { configuration.set(YarnConfiguration.PROXY_ADDRESS, "localhost:9090"); @@ -330,12 +399,13 @@ public void testWebAppProxyPassThroughHeaders() throws Exception { assertEquals(proxyConn.getRequestProperties().size(), 4); proxyConn.connect(); assertEquals(HttpURLConnection.HTTP_OK, proxyConn.getResponseCode()); - // Verify if number of headers received by end server is 8. - // Eight headers include Accept, Host, Connection, User-Agent, Cookie, - // Origin, Access-Control-Request-Method and + // Verify if number of headers received by end server is 9. + // This should match WebAppProxyServlet#PASS_THROUGH_HEADERS. + // Nine headers include Accept, Host, Connection, User-Agent, Cookie, + // Origin, Access-Control-Request-Method, Accept-Encoding, and // Access-Control-Request-Headers. Pls note that Unknown-Header is dropped // by proxy as it is not in the list of allowed headers. - assertEquals(numberOfHeaders, 8); + assertEquals(numberOfHeaders, 9); assertFalse(hasUnknownHeader); } finally { proxy.close(); @@ -553,6 +623,12 @@ public FetchedAppReport getApplicationReport(ApplicationId appId) return result; } else if (answer == 6) { return getDefaultApplicationReport(appId, false); + } else if (answer == 7) { + // test connection timeout + FetchedAppReport result = getDefaultApplicationReport(appId); + result.getApplicationReport().setOriginalTrackingUrl("localhost:" + + originalPort + "/foo/timeout?a=b#main"); + return result; } return null; }