diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/main/java/org/apache/hadoop/yarn/client/cli/LogsCLI.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/main/java/org/apache/hadoop/yarn/client/cli/LogsCLI.java index edee8ee..47d7730 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/main/java/org/apache/hadoop/yarn/client/cli/LogsCLI.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/main/java/org/apache/hadoop/yarn/client/cli/LogsCLI.java @@ -43,10 +43,7 @@ import org.apache.hadoop.conf.Configured; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.util.Tool; -import org.apache.hadoop.yarn.api.records.ApplicationId; -import org.apache.hadoop.yarn.api.records.ApplicationReport; -import org.apache.hadoop.yarn.api.records.ContainerReport; -import org.apache.hadoop.yarn.api.records.YarnApplicationState; +import org.apache.hadoop.yarn.api.records.*; import org.apache.hadoop.yarn.client.api.YarnClient; import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.exceptions.YarnException; @@ -208,13 +205,11 @@ public int run(String[] args) throws Exception { LogCLIHelpers logCliHelper = new LogCLIHelpers(); logCliHelper.setConf(getConf()); - if (appOwner == null || appOwner.isEmpty()) { - appOwner = UserGroupInformation.getCurrentUser().getShortUserName(); - } - YarnApplicationState appState = YarnApplicationState.NEW; + ApplicationReport appReport = null; try { - appState = getApplicationState(appId); + appReport = getApplicationReport(appId); + appState = appReport.getYarnApplicationState(); if (appState == YarnApplicationState.NEW || appState == YarnApplicationState.NEW_SAVING || appState == YarnApplicationState.SUBMITTED) { @@ -226,6 +221,22 @@ public int run(String[] args) throws Exception { + " Attempting to fetch logs directly from the filesystem."); } + if (appReport != null) { + //always use the app owner from the app report if possible + appOwner = appReport.getUser(); + } + else { + if (appOwner == null || appOwner.isEmpty()) { + appOwner = UserGroupInformation.getCurrentUser().getShortUserName(); + } + appOwner = LogCLIHelpers.getOwnerForAppIdOrNull( + appId, appOwner, getConf()); + if (appOwner == null) { + System.out.println("Could not locate application logs for " + appId); + return -1; + } + } + // To get am logs if (getAMContainerLogs) { // if we do not specify the value for CONTAINER_LOG_FILES option, @@ -339,13 +350,12 @@ public int run(String[] args) throws Exception { return resultCode; } - private YarnApplicationState getApplicationState(ApplicationId appId) + private ApplicationReport getApplicationReport(ApplicationId appId) throws IOException, YarnException { YarnClient yarnClient = createYarnClient(); try { - ApplicationReport appReport = yarnClient.getApplicationReport(appId); - return appReport.getYarnApplicationState(); + return yarnClient.getApplicationReport(appId); } finally { yarnClient.close(); } diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/cli/TestLogsCLI.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/cli/TestLogsCLI.java index aec7cae..db50590 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/cli/TestLogsCLI.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-client/src/test/java/org/apache/hadoop/yarn/client/cli/TestLogsCLI.java @@ -69,7 +69,7 @@ public class TestLogsCLI { ByteArrayOutputStream sysOutStream; private PrintStream sysOut; - + ByteArrayOutputStream sysErrStream; private PrintStream sysErr; @@ -78,7 +78,7 @@ public void setUp() { sysOutStream = new ByteArrayOutputStream(); sysOut = new PrintStream(sysOutStream); System.setOut(sysOut); - + sysErrStream = new ByteArrayOutputStream(); sysErr = new PrintStream(sysErrStream); System.setErr(sysErr); @@ -90,16 +90,18 @@ public void testFailResultCodes() throws Exception { conf.setClass("fs.file.impl", LocalFileSystem.class, FileSystem.class); LogCLIHelpers cliHelper = new LogCLIHelpers(); cliHelper.setConf(conf); - YarnClient mockYarnClient = createMockYarnClient(YarnApplicationState.FINISHED); + YarnClient mockYarnClient = createMockYarnClient( + YarnApplicationState.FINISHED, + UserGroupInformation.getCurrentUser().getShortUserName()); LogsCLI dumper = new LogsCLIForTest(mockYarnClient); dumper.setConf(conf); - + // verify dumping a non-existent application's logs returns a failure code int exitCode = dumper.run( new String[] { "-applicationId", "application_0_0" } ); assertTrue("Should return an error code", exitCode != 0); - - // verify dumping a non-existent container log is a failure code + + // verify dumping a non-existent container log is a failure code exitCode = cliHelper.dumpAContainersLogs("application_0_0", "container_0_0", "nonexistentnode:1234", "nobody"); assertTrue("Should return an error code", exitCode != 0); @@ -108,10 +110,12 @@ public void testFailResultCodes() throws Exception { @Test(timeout = 5000l) public void testInvalidApplicationId() throws Exception { Configuration conf = new YarnConfiguration(); - YarnClient mockYarnClient = createMockYarnClient(YarnApplicationState.FINISHED); + YarnClient mockYarnClient = createMockYarnClient( + YarnApplicationState.FINISHED, + UserGroupInformation.getCurrentUser().getShortUserName()); LogsCLI cli = new LogsCLIForTest(mockYarnClient); cli.setConf(conf); - + int exitCode = cli.run( new String[] { "-applicationId", "not_an_app_id"}); assertTrue(exitCode == -1); assertTrue(sysErrStream.toString().startsWith("Invalid ApplicationId specified")); @@ -136,7 +140,9 @@ public void testUnknownApplicationId() throws Exception { @Test(timeout = 5000l) public void testHelpMessage() throws Exception { Configuration conf = new YarnConfiguration(); - YarnClient mockYarnClient = createMockYarnClient(YarnApplicationState.FINISHED); + YarnClient mockYarnClient = createMockYarnClient( + YarnApplicationState.FINISHED, + UserGroupInformation.getCurrentUser().getShortUserName()); LogsCLI dumper = new LogsCLIForTest(mockYarnClient); dumper.setConf(conf); @@ -175,7 +181,7 @@ public void testHelpMessage() throws Exception { String appReportStr = baos.toString("UTF-8"); Assert.assertEquals(appReportStr, sysOutStream.toString()); } - + @Test (timeout = 15000) public void testFetchApplictionLogs() throws Exception { String remoteLogRootDir = "target/logs/"; @@ -246,7 +252,8 @@ public void testFetchApplictionLogs() throws Exception { containerId3, path, fs); YarnClient mockYarnClient = - createMockYarnClient(YarnApplicationState.FINISHED); + createMockYarnClient( + YarnApplicationState.FINISHED, ugi.getShortUserName()); LogsCLI cli = new LogsCLIForTest(mockYarnClient); cli.setConf(configuration); @@ -262,6 +269,15 @@ public void testFetchApplictionLogs() throws Exception { "Hello container_0_0001_01_000003 in stdout!")); sysOutStream.reset(); + // check that we can retrieve logs even if an invalid user is specified + exitCode = cli.run(new String[] { + "-applicationId", appId.toString(), + "-appOwner", "invalid"}); + assertTrue(exitCode == 0); + assertTrue(sysOutStream.toString().contains( + "Hello container_0_0001_01_000001 in syslog!")); + sysOutStream.reset(); + // uploaded two logs for container1. The first log is empty. // The second one is not empty. // We can still successfully read logs for container1. @@ -347,7 +363,8 @@ public void testFetchApplictionLogsHar() throws Exception { assertTrue(fs.exists(harPath)); YarnClient mockYarnClient = - createMockYarnClient(YarnApplicationState.FINISHED); + createMockYarnClient(YarnApplicationState.FINISHED, + ugi.getShortUserName()); LogsCLI cli = new LogsCLIForTest(mockYarnClient); cli.setConf(configuration); int exitCode = cli.run(new String[]{"-applicationId", @@ -429,10 +446,12 @@ private static void uploadEmptyContainerLogIntoRemoteDir(UserGroupInformation ug writer.close(); } - private YarnClient createMockYarnClient(YarnApplicationState appState) + private YarnClient createMockYarnClient(YarnApplicationState appState, + String user) throws YarnException, IOException { YarnClient mockClient = mock(YarnClient.class); ApplicationReport mockAppReport = mock(ApplicationReport.class); + doReturn(user).when(mockAppReport).getUser(); doReturn(appState).when(mockAppReport).getYarnApplicationState(); doReturn(mockAppReport).when(mockClient).getApplicationReport( any(ApplicationId.class)); @@ -448,9 +467,9 @@ private YarnClient createMockYarnClientUnknownApp() throws YarnException, } private static class LogsCLIForTest extends LogsCLI { - + private YarnClient yarnClient; - + public LogsCLIForTest(YarnClient yarnClient) { super(); this.yarnClient = yarnClient; diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/logaggregation/LogCLIHelpers.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/logaggregation/LogCLIHelpers.java index fb4d3cd..5385d24 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/logaggregation/LogCLIHelpers.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/logaggregation/LogCLIHelpers.java @@ -56,6 +56,46 @@ public int dumpAContainersLogs(String appId, String containerId, @Private @VisibleForTesting + /** + * Return the owner for a given AppId + * @param remoteRootLogDir + * @param appId + * @param bestGuess + * @param conf + * @return the owner or null + * @throws IOException + */ + public static String getOwnerForAppIdOrNull( + ApplicationId appId, String bestGuess, + Configuration conf) throws IOException { + Path remoteRootLogDir = new Path(conf.get( + YarnConfiguration.NM_REMOTE_APP_LOG_DIR, + YarnConfiguration.DEFAULT_NM_REMOTE_APP_LOG_DIR)); + String suffix = LogAggregationUtils.getRemoteNodeLogDirSuffix(conf); + Path fullPath = LogAggregationUtils.getRemoteAppLogDir(remoteRootLogDir, + appId, bestGuess, suffix); + FileContext fc = + FileContext.getFileContext(remoteRootLogDir.toUri(), conf); + if (fc.util().exists(fullPath)) { + return bestGuess; + } + Path toMatch = LogAggregationUtils. + getRemoteAppLogDir(remoteRootLogDir, appId, "*", suffix); + FileStatus[] matching = fc.util().globStatus(toMatch); + if (matching == null || matching.length != 1) { + return null; + } + //fetch the user from the full path /app-logs/user[/suffix]/app_id + Path parent = matching[0].getPath().getParent(); + //skip the suffix too + if (suffix != null && !StringUtils.isEmpty(suffix)) { + parent = parent.getParent(); + } + return parent.getName(); + } + + @Private + @VisibleForTesting public int dumpAContainersLogsForALogType(String appId, String containerId, String nodeId, String jobOwner, List logType) throws IOException { Path remoteRootLogDir = new Path(getConf().get(