diff --git 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-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java index d446c8f..02e5c99 100644 --- 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-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java @@ -1021,7 +1021,10 @@ public static boolean isAclEnabled(Configuration conf) { public static final String YARN_LOG_SERVER_URL = YARN_PREFIX + "log.server.url"; - + + public static final String YARN_LOG_SERVER_WEBSERVICE_URL = + YARN_PREFIX + "log.server.web-service.url"; + public static final String YARN_TRACKING_URL_GENERATOR = YARN_PREFIX + "tracking.url.generator"; diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/NMWebServices.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/NMWebServices.java index 22b85dc..30747a4 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/NMWebServices.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/NMWebServices.java @@ -23,6 +23,7 @@ import java.io.OutputStream; import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Map.Entry; @@ -47,6 +48,7 @@ import org.apache.hadoop.classification.InterfaceStability.Unstable; import org.apache.hadoop.yarn.api.records.ApplicationId; import org.apache.hadoop.yarn.api.records.ContainerId; +import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.exceptions.YarnException; import org.apache.hadoop.yarn.factories.RecordFactory; import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider; @@ -72,6 +74,8 @@ import org.apache.hadoop.yarn.webapp.NotFoundException; import org.apache.hadoop.yarn.webapp.WebApp; import org.apache.hadoop.yarn.webapp.util.WebAppUtils; +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -83,6 +87,7 @@ private WebApp webapp; private static RecordFactory recordFactory = RecordFactoryProvider .getRecordFactory(null); + private final String redirectWSUrl; private @javax.ws.rs.core.Context HttpServletRequest request; @@ -99,6 +104,8 @@ public NMWebServices(final Context nm, final ResourceView view, this.nmContext = nm; this.rview = view; this.webapp = webapp; + this.redirectWSUrl = this.nmContext.getConf().get( + YarnConfiguration.YARN_LOG_SERVER_WEBSERVICE_URL); } private void init() { @@ -268,7 +275,13 @@ public Response getContainerLogsInfo( resp.header("X-Content-Type-Options", "nosniff"); return resp.build(); } catch (Exception ex) { - throw new WebApplicationException(ex); + if (redirectWSUrl == null || redirectWSUrl.isEmpty()) { + throw new WebApplicationException(ex); + } + // redirect the request to the configured log server + String redirectURI = "/containers/" + containerIdStr + + "/logs"; + return createRedirectResponse(request, redirectWSUrl, redirectURI); } } @@ -363,7 +376,14 @@ public Response getLogs( logFile = ContainerLogsUtils.getContainerLogFile( containerId, filename, request.getRemoteUser(), nmContext); } catch (NotFoundException ex) { - return Response.status(Status.NOT_FOUND).entity(ex.getMessage()).build(); + if (redirectWSUrl == null || redirectWSUrl.isEmpty()) { + return Response.status(Status.NOT_FOUND).entity(ex.getMessage()) + .build(); + } + // redirect the request to the configured log server + String redirectURI = "/containers/" + containerIdStr + + "/logs/" + filename; + return createRedirectResponse(request, redirectWSUrl, redirectURI); } catch (YarnException ex) { return Response.serverError().entity(ex.getMessage()).build(); } @@ -445,4 +465,47 @@ private long parseLongParam(String bytes) { } return Long.parseLong(bytes); } + + private String removeSpecifiedQueryParameter(HttpServletRequest request, + String parameterName) { + String queryString = request.getQueryString(); + if (queryString != null && !queryString.isEmpty()) { + String reqEncoding = request.getCharacterEncoding(); + if (reqEncoding == null || reqEncoding.isEmpty()) { + reqEncoding = "ISO-8859-1"; + } + Charset encoding = Charset.forName(reqEncoding); + List params = URLEncodedUtils.parse(queryString, encoding); + Iterator paramIterator = params.iterator(); + while(paramIterator.hasNext()) { + NameValuePair current = paramIterator.next(); + if (current.getName().equals(parameterName)) { + paramIterator.remove(); + } + } + return URLEncodedUtils.format(params, encoding); + } + return null; + } + + private Response createRedirectResponse(HttpServletRequest request, + String redirectWSUrlPrefix, String uri) { + // redirect the request to the configured log server + StringBuilder redirectPath = new StringBuilder(); + if (redirectWSUrlPrefix.endsWith("/")) { + redirectWSUrlPrefix = redirectWSUrlPrefix.substring(0, + redirectWSUrlPrefix.length() - 1); + } + redirectPath.append(redirectWSUrlPrefix + uri); + // append all the request query parameters except nmwebaddress parameter + String requestParams = removeSpecifiedQueryParameter(request, + YarnWebServiceParams.NM_WEB_ADDRESS); + if (requestParams != null && !requestParams.isEmpty()) { + redirectPath.append("?" + requestParams); + } + ResponseBuilder response = Response.status( + HttpServletResponse.SC_TEMPORARY_REDIRECT); + response.header("Location", redirectPath.toString()); + return response.build(); + } } diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/webapp/TestNMWebServices.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/webapp/TestNMWebServices.java index de1e5cc..1313c11 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/webapp/TestNMWebServices.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/webapp/TestNMWebServices.java @@ -19,6 +19,7 @@ package org.apache.hadoop.yarn.server.nodemanager.webapp; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -26,7 +27,11 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.StringReader; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; import java.util.List; +import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.MediaType; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -57,6 +62,7 @@ import org.apache.hadoop.yarn.server.nodemanager.webapp.WebServer.NMWebApp; import org.apache.hadoop.yarn.server.security.ApplicationACLsManager; import org.apache.hadoop.yarn.server.utils.BuilderUtils; +import org.apache.hadoop.yarn.server.webapp.YarnWebServiceParams; import org.apache.hadoop.yarn.server.webapp.dao.ContainerLogsInfo; import org.apache.hadoop.yarn.util.YarnVersionInfo; import org.apache.hadoop.yarn.webapp.GenericExceptionHandler; @@ -96,6 +102,7 @@ private static ApplicationACLsManager aclsManager; private static LocalDirsHandlerService dirsHandler; private static WebApp nmWebApp; + private static final String logServiceWSAddress = "test:1234"; private static final File testRootDir = new File("target", TestNMWebServices.class.getSimpleName()); @@ -114,6 +121,8 @@ protected void configureServlets() { conf.setBoolean(YarnConfiguration.LOG_AGGREGATION_ENABLED, true); conf.set(YarnConfiguration.NM_REMOTE_APP_LOG_DIR, testRemoteLogDir.getAbsolutePath()); + conf.set(YarnConfiguration.YARN_LOG_SERVER_WEBSERVICE_URL, + logServiceWSAddress); dirsHandler = new LocalDirsHandlerService(); NodeHealthCheckerService healthChecker = new NodeHealthCheckerService( NodeManager.getNodeHealthScriptRunner(conf), dirsHandler); @@ -344,6 +353,58 @@ public void testContainerLogsWithOldAPI() throws IOException, JSONException{ testContainerLogs(r, containerId); } + @Test (timeout = 10000) + public void testNMRedirect() { + ApplicationId noExistAppId = ApplicationId.newInstance( + System.currentTimeMillis(), 2000); + ApplicationAttemptId noExistAttemptId = ApplicationAttemptId.newInstance( + noExistAppId, 150); + ContainerId noExistContainerId = ContainerId.newContainerId( + noExistAttemptId, 250); + String fileName = "syslog"; + WebResource r = resource(); + + // check the old api + URI requestURI = r.path("ws").path("v1").path("node") + .path("containerlogs").path(noExistContainerId.toString()) + .path(fileName).queryParam("user.name", "user") + .queryParam(YarnWebServiceParams.NM_WEB_ADDRESS, "localhost:1111") + .getURI(); + String redirectURL = getRedirectURL(requestURI.toString()); + assertTrue(redirectURL != null); + assertTrue(redirectURL.contains(logServiceWSAddress)); + assertTrue(redirectURL.contains(noExistContainerId.toString())); + assertTrue(redirectURL.contains("/logs/" + fileName)); + assertTrue(redirectURL.contains("user.name=" + "user")); + assertFalse(redirectURL.contains(YarnWebServiceParams.NM_WEB_ADDRESS)); + + // check the new api + requestURI = r.path("ws").path("v1").path("node") + .path("containers").path(noExistContainerId.toString()) + .path("logs").path(fileName).queryParam("user.name", "user") + .queryParam(YarnWebServiceParams.NM_WEB_ADDRESS, "localhost:1111") + .getURI(); + redirectURL = getRedirectURL(requestURI.toString()); + assertTrue(redirectURL != null); + assertTrue(redirectURL.contains(logServiceWSAddress)); + assertTrue(redirectURL.contains(noExistContainerId.toString())); + assertTrue(redirectURL.contains("/logs/" + fileName)); + assertTrue(redirectURL.contains("user.name=" + "user")); + assertFalse(redirectURL.contains(YarnWebServiceParams.NM_WEB_ADDRESS)); + + requestURI = r.path("ws").path("v1").path("node") + .path("containers").path(noExistContainerId.toString()) + .path("logs").queryParam("user.name", "user") + .queryParam(YarnWebServiceParams.NM_WEB_ADDRESS, "localhost:1111") + .getURI(); + redirectURL = getRedirectURL(requestURI.toString()); + assertTrue(redirectURL != null); + assertTrue(redirectURL.contains(logServiceWSAddress)); + assertTrue(redirectURL.contains(noExistContainerId.toString())); + assertTrue(redirectURL.contains("user.name=" + "user")); + assertFalse(redirectURL.contains(YarnWebServiceParams.NM_WEB_ADDRESS)); + } + private void testContainerLogs(WebResource r, ContainerId containerId) throws IOException { final String containerIdStr = containerId.toString(); @@ -443,12 +504,12 @@ private void testContainerLogs(WebResource r, ContainerId containerId) + WebAppUtils.listSupportedLogContentType(), responseText); assertEquals(400, response.getStatus()); - // ask for file that doesn't exist + // ask for file that doesn't exist and it will re-direct to + // the log server response = r.path("uhhh") .accept(MediaType.TEXT_PLAIN).get(ClientResponse.class); - Assert.assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus()); - responseText = response.getEntity(String.class); - assertTrue(responseText.contains("Cannot find this log on the local disk.")); + Assert.assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(), + response.getStatus()); // Get container log files' name WebResource r1 = resource(); @@ -620,4 +681,21 @@ private String getLogContext(String fullMessage) { int postfixIndex = fullMessage.indexOf(postfix); return fullMessage.substring(prefixIndex, postfixIndex); } -} + + private static String getRedirectURL(String url) { + String redirectUrl = null; + try { + HttpURLConnection conn = (HttpURLConnection) new URL(url) + .openConnection(); + // do not automatically follow the redirection + // otherwise we get too many redirections exception + conn.setInstanceFollowRedirects(false); + if(conn.getResponseCode() == HttpServletResponse.SC_TEMPORARY_REDIRECT) { + redirectUrl = conn.getHeaderField("Location"); + } + } catch (Exception e) { + // throw new RuntimeException(e); + } + return redirectUrl; + } +} \ No newline at end of file