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 7887fbc..136227a 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 @@ -1077,7 +1077,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-common/src/main/resources/yarn-default.xml hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml index 1e929a8..4ca46f9 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml @@ -2650,6 +2650,14 @@ + URL for log aggregation server web service + + yarn.log.server.web-service.url + + + + + RM Application Tracking URL yarn.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 a59e010..26416af 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; @@ -50,6 +51,7 @@ import org.apache.hadoop.http.JettyUtils; 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; @@ -75,6 +77,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; @@ -87,6 +91,7 @@ private WebApp webapp; private static RecordFactory recordFactory = RecordFactoryProvider .getRecordFactory(null); + private final String redirectWSUrl; private @javax.ws.rs.core.Context HttpServletRequest request; @@ -103,6 +108,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() { @@ -270,6 +277,9 @@ public Response getContainerLogsInfo( } catch (IOException ex) { // Something wrong with we tries to access the remote fs for the logs. // Skip it and do nothing + if (LOG.isDebugEnabled()) { + LOG.debug(ex.getMessage()); + } } GenericEntity> meta = new GenericEntity>(containersLogsInfo){}; @@ -280,7 +290,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(hsr, redirectWSUrl, redirectURI); } } @@ -377,7 +393,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(); } @@ -464,4 +487,47 @@ private long parseLongParam(String bytes) { } return Long.parseLong(bytes); } + + private String removeSpecifiedQueryParameter(HttpServletRequest httpRequest, + String parameterName) { + String queryString = httpRequest.getQueryString(); + if (queryString != null && !queryString.isEmpty()) { + String reqEncoding = httpRequest.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 httpRequest, + 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 nodeId parameter + String requestParams = removeSpecifiedQueryParameter(httpRequest, + YarnWebServiceParams.NM_ID); + if (requestParams != null && !requestParams.isEmpty()) { + redirectPath.append("?" + requestParams); + } + ResponseBuilder res = Response.status( + HttpServletResponse.SC_TEMPORARY_REDIRECT); + res.header("Location", redirectPath.toString()); + return res.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 7764ceb..e3773d9 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 @@ -20,6 +20,7 @@ import static org.apache.hadoop.yarn.webapp.WebServicesTestUtils.assertResponseStatusCode; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -27,7 +28,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; @@ -59,6 +64,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; @@ -97,6 +103,7 @@ private static ApplicationACLsManager aclsManager; private static LocalDirsHandlerService dirsHandler; private static WebApp nmWebApp; + private static final String LOGSERVICEWSADDR = "test:1234"; private static final File testRootDir = new File("target", TestNMWebServices.class.getSimpleName()); @@ -115,6 +122,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, + LOGSERVICEWSADDR); dirsHandler = new LocalDirsHandlerService(); NodeHealthCheckerService healthChecker = new NodeHealthCheckerService( NodeManager.getNodeHealthScriptRunner(conf), dirsHandler); @@ -351,6 +360,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_ID, "localhost:1111") + .getURI(); + String redirectURL = getRedirectURL(requestURI.toString()); + assertTrue(redirectURL != null); + assertTrue(redirectURL.contains(LOGSERVICEWSADDR)); + assertTrue(redirectURL.contains(noExistContainerId.toString())); + assertTrue(redirectURL.contains("/logs/" + fileName)); + assertTrue(redirectURL.contains("user.name=" + "user")); + assertFalse(redirectURL.contains(YarnWebServiceParams.NM_ID)); + + // 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_ID, "localhost:1111") + .getURI(); + redirectURL = getRedirectURL(requestURI.toString()); + assertTrue(redirectURL != null); + assertTrue(redirectURL.contains(LOGSERVICEWSADDR)); + assertTrue(redirectURL.contains(noExistContainerId.toString())); + assertTrue(redirectURL.contains("/logs/" + fileName)); + assertTrue(redirectURL.contains("user.name=" + "user")); + assertFalse(redirectURL.contains(YarnWebServiceParams.NM_ID)); + + requestURI = r.path("ws").path("v1").path("node") + .path("containers").path(noExistContainerId.toString()) + .path("logs").queryParam("user.name", "user") + .queryParam(YarnWebServiceParams.NM_ID, "localhost:1111") + .getURI(); + redirectURL = getRedirectURL(requestURI.toString()); + assertTrue(redirectURL != null); + assertTrue(redirectURL.contains(LOGSERVICEWSADDR)); + assertTrue(redirectURL.contains(noExistContainerId.toString())); + assertTrue(redirectURL.contains("user.name=" + "user")); + assertFalse(redirectURL.contains(YarnWebServiceParams.NM_ID)); + } + private void testContainerLogs(WebResource r, ContainerId containerId) throws IOException { final String containerIdStr = containerId.toString(); @@ -451,13 +512,12 @@ private void testContainerLogs(WebResource r, ContainerId containerId) + WebAppUtils.listSupportedLogContentType(), responseText); assertEquals(400, response.getStatus()); - // ask for file that doesn't exist - response = r.path("uhhh") - .accept(MediaType.TEXT_PLAIN).get(ClientResponse.class); - assertEquals(Status.NOT_FOUND.getStatusCode(), - response.getStatus()); - responseText = response.getEntity(String.class); - assertTrue(responseText.contains("Cannot find this log on the local disk.")); + // ask for file that doesn't exist and it will re-direct to + // the log server + URI requestURI = r.path("uhhh").getURI(); + String redirectURL = getRedirectURL(requestURI.toString()); + assertTrue(redirectURL != null); + assertTrue(redirectURL.contains(LOGSERVICEWSADDR)); // Get container log files' name WebResource r1 = resource(); @@ -630,4 +690,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