MAPREDUCE-2989. Modified JobHistory to link to task and AM logs from the JobHistoryServer. Contributed by Siddharth Seth.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1190174 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
47c28ce14d
commit
670fa24b48
|
@ -441,6 +441,9 @@ Release 0.23.0 - Unreleased
|
||||||
|
|
||||||
MAPREDUCE-2736. Remove unused contrib components dependent on MR1. (eli)
|
MAPREDUCE-2736. Remove unused contrib components dependent on MR1. (eli)
|
||||||
|
|
||||||
|
MAPREDUCE-2989. Modified JobHistory to link to task and AM logs from the
|
||||||
|
JobHistoryServer. (Siddharth Seth via vinodkv)
|
||||||
|
|
||||||
OPTIMIZATIONS
|
OPTIMIZATIONS
|
||||||
|
|
||||||
MAPREDUCE-2026. Make JobTracker.getJobCounters() and
|
MAPREDUCE-2026. Make JobTracker.getJobCounters() and
|
||||||
|
|
|
@ -22,7 +22,6 @@ import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.security.PrivilegedExceptionAction;
|
import java.security.PrivilegedExceptionAction;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -85,7 +84,6 @@ import org.apache.hadoop.mapreduce.v2.app.taskclean.TaskCleaner;
|
||||||
import org.apache.hadoop.mapreduce.v2.app.taskclean.TaskCleanerImpl;
|
import org.apache.hadoop.mapreduce.v2.app.taskclean.TaskCleanerImpl;
|
||||||
import org.apache.hadoop.mapreduce.v2.util.MRBuilderUtils;
|
import org.apache.hadoop.mapreduce.v2.util.MRBuilderUtils;
|
||||||
import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
|
import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
|
||||||
import org.apache.hadoop.net.NetUtils;
|
|
||||||
import org.apache.hadoop.security.Credentials;
|
import org.apache.hadoop.security.Credentials;
|
||||||
import org.apache.hadoop.security.UserGroupInformation;
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
import org.apache.hadoop.security.token.Token;
|
import org.apache.hadoop.security.token.Token;
|
||||||
|
@ -142,6 +140,7 @@ public class MRAppMaster extends CompositeService {
|
||||||
private final ApplicationAttemptId appAttemptID;
|
private final ApplicationAttemptId appAttemptID;
|
||||||
private final ContainerId containerID;
|
private final ContainerId containerID;
|
||||||
private final String nmHost;
|
private final String nmHost;
|
||||||
|
private final int nmPort;
|
||||||
private final int nmHttpPort;
|
private final int nmHttpPort;
|
||||||
protected final MRAppMetrics metrics;
|
protected final MRAppMetrics metrics;
|
||||||
private Set<TaskId> completedTasksFromPreviousRun;
|
private Set<TaskId> completedTasksFromPreviousRun;
|
||||||
|
@ -168,14 +167,15 @@ public class MRAppMaster extends CompositeService {
|
||||||
private UserGroupInformation currentUser; // Will be setup during init
|
private UserGroupInformation currentUser; // Will be setup during init
|
||||||
|
|
||||||
public MRAppMaster(ApplicationAttemptId applicationAttemptId,
|
public MRAppMaster(ApplicationAttemptId applicationAttemptId,
|
||||||
ContainerId containerId, String nmHost, int nmHttpPort, long appSubmitTime) {
|
ContainerId containerId, String nmHost, int nmPort, int nmHttpPort,
|
||||||
this(applicationAttemptId, containerId, nmHost, nmHttpPort,
|
long appSubmitTime) {
|
||||||
|
this(applicationAttemptId, containerId, nmHost, nmPort, nmHttpPort,
|
||||||
new SystemClock(), appSubmitTime);
|
new SystemClock(), appSubmitTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MRAppMaster(ApplicationAttemptId applicationAttemptId,
|
public MRAppMaster(ApplicationAttemptId applicationAttemptId,
|
||||||
ContainerId containerId, String nmHost, int nmHttpPort, Clock clock,
|
ContainerId containerId, String nmHost, int nmPort, int nmHttpPort,
|
||||||
long appSubmitTime) {
|
Clock clock, long appSubmitTime) {
|
||||||
super(MRAppMaster.class.getName());
|
super(MRAppMaster.class.getName());
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
this.startTime = clock.getTime();
|
this.startTime = clock.getTime();
|
||||||
|
@ -183,6 +183,7 @@ public class MRAppMaster extends CompositeService {
|
||||||
this.appAttemptID = applicationAttemptId;
|
this.appAttemptID = applicationAttemptId;
|
||||||
this.containerID = containerId;
|
this.containerID = containerId;
|
||||||
this.nmHost = nmHost;
|
this.nmHost = nmHost;
|
||||||
|
this.nmPort = nmPort;
|
||||||
this.nmHttpPort = nmHttpPort;
|
this.nmHttpPort = nmHttpPort;
|
||||||
this.metrics = MRAppMetrics.create();
|
this.metrics = MRAppMetrics.create();
|
||||||
LOG.info("Created MRAppMaster for application " + applicationAttemptId);
|
LOG.info("Created MRAppMaster for application " + applicationAttemptId);
|
||||||
|
@ -757,7 +758,8 @@ public class MRAppMaster extends CompositeService {
|
||||||
amInfos = new LinkedList<AMInfo>();
|
amInfos = new LinkedList<AMInfo>();
|
||||||
}
|
}
|
||||||
AMInfo amInfo =
|
AMInfo amInfo =
|
||||||
new AMInfo(appAttemptID, startTime, containerID, nmHost, nmHttpPort);
|
new AMInfo(appAttemptID, startTime, containerID, nmHost, nmPort,
|
||||||
|
nmHttpPort);
|
||||||
amInfos.add(amInfo);
|
amInfos.add(amInfo);
|
||||||
|
|
||||||
// /////////////////// Create the job itself.
|
// /////////////////// Create the job itself.
|
||||||
|
@ -770,7 +772,8 @@ public class MRAppMaster extends CompositeService {
|
||||||
dispatcher.getEventHandler().handle(
|
dispatcher.getEventHandler().handle(
|
||||||
new JobHistoryEvent(job.getID(), new AMStartedEvent(info
|
new JobHistoryEvent(job.getID(), new AMStartedEvent(info
|
||||||
.getAppAttemptId(), info.getStartTime(), info.getContainerId(),
|
.getAppAttemptId(), info.getStartTime(), info.getContainerId(),
|
||||||
info.getNodeManagerHost(), info.getNodeManagerHttpPort())));
|
info.getNodeManagerHost(), info.getNodeManagerPort(), info
|
||||||
|
.getNodeManagerHttpPort())));
|
||||||
}
|
}
|
||||||
|
|
||||||
// metrics system init is really init & start.
|
// metrics system init is really init & start.
|
||||||
|
@ -872,41 +875,44 @@ public class MRAppMaster extends CompositeService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void validateInputParam(String value, String param)
|
||||||
|
throws IOException {
|
||||||
|
if (value == null) {
|
||||||
|
String msg = param + " is null";
|
||||||
|
LOG.error(msg);
|
||||||
|
throw new IOException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
try {
|
try {
|
||||||
String containerIdStr =
|
String containerIdStr =
|
||||||
System.getenv(ApplicationConstants.AM_CONTAINER_ID_ENV);
|
System.getenv(ApplicationConstants.AM_CONTAINER_ID_ENV);
|
||||||
String nodeHttpAddressStr =
|
String nodeHostString = System.getenv(ApplicationConstants.NM_HOST_ENV);
|
||||||
System.getenv(ApplicationConstants.NM_HTTP_ADDRESS_ENV);
|
String nodePortString = System.getenv(ApplicationConstants.NM_PORT_ENV);
|
||||||
|
String nodeHttpPortString =
|
||||||
|
System.getenv(ApplicationConstants.NM_HTTP_PORT_ENV);
|
||||||
String appSubmitTimeStr =
|
String appSubmitTimeStr =
|
||||||
System.getenv(ApplicationConstants.APP_SUBMIT_TIME_ENV);
|
System.getenv(ApplicationConstants.APP_SUBMIT_TIME_ENV);
|
||||||
if (containerIdStr == null) {
|
|
||||||
String msg = ApplicationConstants.AM_CONTAINER_ID_ENV + " is null";
|
validateInputParam(containerIdStr,
|
||||||
LOG.error(msg);
|
ApplicationConstants.AM_CONTAINER_ID_ENV);
|
||||||
throw new IOException(msg);
|
validateInputParam(nodeHostString, ApplicationConstants.NM_HOST_ENV);
|
||||||
}
|
validateInputParam(nodePortString, ApplicationConstants.NM_PORT_ENV);
|
||||||
if (nodeHttpAddressStr == null) {
|
validateInputParam(nodeHttpPortString,
|
||||||
String msg = ApplicationConstants.NM_HTTP_ADDRESS_ENV + " is null";
|
ApplicationConstants.NM_HTTP_PORT_ENV);
|
||||||
LOG.error(msg);
|
validateInputParam(appSubmitTimeStr,
|
||||||
throw new IOException(msg);
|
ApplicationConstants.APP_SUBMIT_TIME_ENV);
|
||||||
}
|
|
||||||
if (appSubmitTimeStr == null) {
|
|
||||||
String msg = ApplicationConstants.APP_SUBMIT_TIME_ENV + " is null";
|
|
||||||
LOG.error(msg);
|
|
||||||
throw new IOException(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
ContainerId containerId = ConverterUtils.toContainerId(containerIdStr);
|
ContainerId containerId = ConverterUtils.toContainerId(containerIdStr);
|
||||||
ApplicationAttemptId applicationAttemptId =
|
ApplicationAttemptId applicationAttemptId =
|
||||||
containerId.getApplicationAttemptId();
|
containerId.getApplicationAttemptId();
|
||||||
InetSocketAddress nodeHttpInetAddr =
|
|
||||||
NetUtils.createSocketAddr(nodeHttpAddressStr);
|
|
||||||
long appSubmitTime = Long.parseLong(appSubmitTimeStr);
|
long appSubmitTime = Long.parseLong(appSubmitTimeStr);
|
||||||
|
|
||||||
MRAppMaster appMaster =
|
MRAppMaster appMaster =
|
||||||
new MRAppMaster(applicationAttemptId, containerId,
|
new MRAppMaster(applicationAttemptId, containerId, nodeHostString,
|
||||||
nodeHttpInetAddr.getHostName(), nodeHttpInetAddr.getPort(),
|
Integer.parseInt(nodePortString),
|
||||||
appSubmitTime);
|
Integer.parseInt(nodeHttpPortString), appSubmitTime);
|
||||||
Runtime.getRuntime().addShutdownHook(
|
Runtime.getRuntime().addShutdownHook(
|
||||||
new CompositeServiceShutdownHook(appMaster));
|
new CompositeServiceShutdownHook(appMaster));
|
||||||
YarnConfiguration conf = new YarnConfiguration(new JobConf());
|
YarnConfiguration conf = new YarnConfiguration(new JobConf());
|
||||||
|
|
|
@ -80,6 +80,7 @@ import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider;
|
||||||
import org.apache.hadoop.yarn.service.CompositeService;
|
import org.apache.hadoop.yarn.service.CompositeService;
|
||||||
import org.apache.hadoop.yarn.service.Service;
|
import org.apache.hadoop.yarn.service.Service;
|
||||||
import org.apache.hadoop.yarn.util.BuilderUtils;
|
import org.apache.hadoop.yarn.util.BuilderUtils;
|
||||||
|
import org.apache.hadoop.yarn.util.ConverterUtils;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Recovers the completed tasks from the previous life of Application Master.
|
* Recovers the completed tasks from the previous life of Application Master.
|
||||||
|
@ -393,9 +394,8 @@ public class RecoveryService extends CompositeService implements Recovery {
|
||||||
TaskAttemptInfo attemptInfo) {
|
TaskAttemptInfo attemptInfo) {
|
||||||
LOG.info("Sending assigned event to " + yarnAttemptID);
|
LOG.info("Sending assigned event to " + yarnAttemptID);
|
||||||
ContainerId cId = attemptInfo.getContainerId();
|
ContainerId cId = attemptInfo.getContainerId();
|
||||||
String[] splits = attemptInfo.getHostname().split(":");
|
|
||||||
NodeId nodeId = BuilderUtils.newNodeId(splits[0], Integer
|
NodeId nodeId = ConverterUtils.toNodeId(attemptInfo.getHostname());
|
||||||
.parseInt(splits[1]));
|
|
||||||
// Resource/Priority/ApplicationACLs are only needed while launching the
|
// Resource/Priority/ApplicationACLs are only needed while launching the
|
||||||
// container on an NM, these are already completed tasks, so setting them
|
// container on an NM, these are already completed tasks, so setting them
|
||||||
// to null
|
// to null
|
||||||
|
|
|
@ -18,10 +18,13 @@
|
||||||
|
|
||||||
package org.apache.hadoop.mapreduce.v2.app.webapp;
|
package org.apache.hadoop.mapreduce.v2.app.webapp;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
import static org.apache.hadoop.mapreduce.v2.app.webapp.AMWebApp.*;
|
import static org.apache.hadoop.mapreduce.v2.app.webapp.AMWebApp.*;
|
||||||
|
|
||||||
|
import org.apache.hadoop.mapreduce.jobhistory.JobHistoryParser.AMInfo;
|
||||||
import org.apache.hadoop.mapreduce.v2.util.MRApps;
|
import org.apache.hadoop.mapreduce.v2.util.MRApps;
|
||||||
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet;
|
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet;
|
||||||
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.*;
|
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.*;
|
||||||
|
@ -47,6 +50,10 @@ public class NavBlock extends HtmlBlock {
|
||||||
li().a(url("app"), "Jobs")._()._();
|
li().a(url("app"), "Jobs")._()._();
|
||||||
if (app.getJob() != null) {
|
if (app.getJob() != null) {
|
||||||
String jobid = MRApps.toString(app.getJob().getID());
|
String jobid = MRApps.toString(app.getJob().getID());
|
||||||
|
List<AMInfo> amInfos = app.getJob().getAMInfos();
|
||||||
|
AMInfo thisAmInfo = amInfos.get(amInfos.size()-1);
|
||||||
|
String nodeHttpAddress = thisAmInfo.getNodeManagerHost() + ":"
|
||||||
|
+ thisAmInfo.getNodeManagerHttpPort();
|
||||||
nav.
|
nav.
|
||||||
h3("Job").
|
h3("Job").
|
||||||
ul().
|
ul().
|
||||||
|
@ -54,7 +61,11 @@ public class NavBlock extends HtmlBlock {
|
||||||
li().a(url("jobcounters", jobid), "Counters")._().
|
li().a(url("jobcounters", jobid), "Counters")._().
|
||||||
li().a(url("conf", jobid), "Configuration")._().
|
li().a(url("conf", jobid), "Configuration")._().
|
||||||
li().a(url("tasks", jobid, "m"), "Map tasks")._().
|
li().a(url("tasks", jobid, "m"), "Map tasks")._().
|
||||||
li().a(url("tasks", jobid, "r"), "Reduce tasks")._()._();
|
li().a(url("tasks", jobid, "r"), "Reduce tasks")._().
|
||||||
|
li().a(".logslink", url("http://", nodeHttpAddress, "node",
|
||||||
|
"containerlogs", thisAmInfo.getContainerId().toString(),
|
||||||
|
app.getJob().getUserName()),
|
||||||
|
"AM Logs")._()._();
|
||||||
if (app.getTask() != null) {
|
if (app.getTask() != null) {
|
||||||
String taskid = MRApps.toString(app.getTask().getID());
|
String taskid = MRApps.toString(app.getTask().getID());
|
||||||
nav.
|
nav.
|
||||||
|
|
|
@ -86,7 +86,7 @@ public class TaskPage extends AppView {
|
||||||
String containerIdStr = ConverterUtils.toString(containerId);
|
String containerIdStr = ConverterUtils.toString(containerId);
|
||||||
nodeTd._(" ").
|
nodeTd._(" ").
|
||||||
a(".logslink", url("http://", nodeHttpAddr, "node", "containerlogs",
|
a(".logslink", url("http://", nodeHttpAddr, "node", "containerlogs",
|
||||||
containerIdStr), "logs");
|
containerIdStr, app.getJob().getUserName()), "logs");
|
||||||
}
|
}
|
||||||
nodeTd._().
|
nodeTd._().
|
||||||
td(".ts", Times.format(startTime)).
|
td(".ts", Times.format(startTime)).
|
||||||
|
|
|
@ -136,7 +136,7 @@ public class MRApp extends MRAppMaster {
|
||||||
public MRApp(int maps, int reduces, boolean autoComplete, String testName,
|
public MRApp(int maps, int reduces, boolean autoComplete, String testName,
|
||||||
boolean cleanOnStart, int startCount) {
|
boolean cleanOnStart, int startCount) {
|
||||||
super(getApplicationAttemptId(applicationId, startCount), getContainerId(
|
super(getApplicationAttemptId(applicationId, startCount), getContainerId(
|
||||||
applicationId, startCount), "testhost", 3333, System.currentTimeMillis());
|
applicationId, startCount), "testhost", 2222, 3333, System.currentTimeMillis());
|
||||||
this.testWorkDir = new File("target", testName);
|
this.testWorkDir = new File("target", testName);
|
||||||
testAbsPath = new Path(testWorkDir.getAbsolutePath());
|
testAbsPath = new Path(testWorkDir.getAbsolutePath());
|
||||||
LOG.info("PathUsed: " + testAbsPath);
|
LOG.info("PathUsed: " + testAbsPath);
|
||||||
|
|
|
@ -24,6 +24,7 @@ import com.google.common.collect.Maps;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -58,6 +59,7 @@ import org.apache.hadoop.yarn.MockApps;
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
|
import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
||||||
import org.apache.hadoop.yarn.api.records.ContainerId;
|
import org.apache.hadoop.yarn.api.records.ContainerId;
|
||||||
|
import org.apache.hadoop.yarn.util.BuilderUtils;
|
||||||
import org.apache.hadoop.yarn.util.Records;
|
import org.apache.hadoop.yarn.util.Records;
|
||||||
|
|
||||||
public class MockJobs extends MockApps {
|
public class MockJobs extends MockApps {
|
||||||
|
@ -492,8 +494,21 @@ public class MockJobs extends MockApps {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<AMInfo> getAMInfos() {
|
public List<AMInfo> getAMInfos() {
|
||||||
throw new UnsupportedOperationException("Not supported yet.");
|
List<AMInfo> amInfoList = new LinkedList<AMInfo>();
|
||||||
|
amInfoList.add(createAMInfo(1));
|
||||||
|
amInfoList.add(createAMInfo(2));
|
||||||
|
return amInfoList;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static AMInfo createAMInfo(int attempt) {
|
||||||
|
ApplicationAttemptId appAttemptId =
|
||||||
|
BuilderUtils.newApplicationAttemptId(
|
||||||
|
BuilderUtils.newApplicationId(100, 1), attempt);
|
||||||
|
ContainerId containerId = BuilderUtils.newContainerId(appAttemptId, 1);
|
||||||
|
return new AMInfo(appAttemptId, System.currentTimeMillis(), containerId,
|
||||||
|
"testhost", 2222, 3333);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ public class TestMRAppMaster {
|
||||||
.toApplicationAttemptId(applicationAttemptIdStr);
|
.toApplicationAttemptId(applicationAttemptIdStr);
|
||||||
ContainerId containerId = ConverterUtils.toContainerId(containerIdStr);
|
ContainerId containerId = ConverterUtils.toContainerId(containerIdStr);
|
||||||
MRAppMasterTest appMaster =
|
MRAppMasterTest appMaster =
|
||||||
new MRAppMasterTest(applicationAttemptId, containerId, "host", -1,
|
new MRAppMasterTest(applicationAttemptId, containerId, "host", -1, -1,
|
||||||
System.currentTimeMillis());
|
System.currentTimeMillis());
|
||||||
YarnConfiguration conf = new YarnConfiguration();
|
YarnConfiguration conf = new YarnConfiguration();
|
||||||
conf.set(MRJobConfig.MR_AM_STAGING_DIR, stagingDir);
|
conf.set(MRJobConfig.MR_AM_STAGING_DIR, stagingDir);
|
||||||
|
@ -60,8 +60,9 @@ class MRAppMasterTest extends MRAppMaster {
|
||||||
private Configuration conf;
|
private Configuration conf;
|
||||||
|
|
||||||
public MRAppMasterTest(ApplicationAttemptId applicationAttemptId,
|
public MRAppMasterTest(ApplicationAttemptId applicationAttemptId,
|
||||||
ContainerId containerId, String host, int port, long submitTime) {
|
ContainerId containerId, String host, int port, int httpPort,
|
||||||
super(applicationAttemptId, containerId, host, port, submitTime);
|
long submitTime) {
|
||||||
|
super(applicationAttemptId, containerId, host, port, httpPort, submitTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -222,6 +222,7 @@ public class TestRecovery {
|
||||||
Assert.assertEquals(amInfo.getAppAttemptId(), amInfo.getContainerId()
|
Assert.assertEquals(amInfo.getAppAttemptId(), amInfo.getContainerId()
|
||||||
.getApplicationAttemptId());
|
.getApplicationAttemptId());
|
||||||
Assert.assertEquals("testhost", amInfo.getNodeManagerHost());
|
Assert.assertEquals("testhost", amInfo.getNodeManagerHost());
|
||||||
|
Assert.assertEquals(2222, amInfo.getNodeManagerPort());
|
||||||
Assert.assertEquals(3333, amInfo.getNodeManagerHttpPort());
|
Assert.assertEquals(3333, amInfo.getNodeManagerHttpPort());
|
||||||
}
|
}
|
||||||
long am1StartTimeReal = job.getAMInfos().get(0).getStartTime();
|
long am1StartTimeReal = job.getAMInfos().get(0).getStartTime();
|
||||||
|
|
|
@ -83,7 +83,7 @@ import org.junit.Test;
|
||||||
|
|
||||||
public TestMRApp(ApplicationAttemptId applicationAttemptId) {
|
public TestMRApp(ApplicationAttemptId applicationAttemptId) {
|
||||||
super(applicationAttemptId, BuilderUtils.newContainerId(
|
super(applicationAttemptId, BuilderUtils.newContainerId(
|
||||||
applicationAttemptId, 1), "testhost", 3333, System
|
applicationAttemptId, 1), "testhost", 2222, 3333, System
|
||||||
.currentTimeMillis());
|
.currentTimeMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,7 @@
|
||||||
{"name": "startTime", "type": "long"},
|
{"name": "startTime", "type": "long"},
|
||||||
{"name": "containerId", "type": "string"},
|
{"name": "containerId", "type": "string"},
|
||||||
{"name": "nodeManagerHost", "type": "string"},
|
{"name": "nodeManagerHost", "type": "string"},
|
||||||
|
{"name": "nodeManagerPort", "type": "int"},
|
||||||
{"name": "nodeManagerHttpPort", "type": "int"}
|
{"name": "nodeManagerHttpPort", "type": "int"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -354,7 +354,7 @@ class JobSubmitter {
|
||||||
|
|
||||||
copyAndConfigureFiles(job, submitJobDir);
|
copyAndConfigureFiles(job, submitJobDir);
|
||||||
Path submitJobFile = JobSubmissionFiles.getJobConfPath(submitJobDir);
|
Path submitJobFile = JobSubmissionFiles.getJobConfPath(submitJobDir);
|
||||||
|
|
||||||
// Create the splits for the job
|
// Create the splits for the job
|
||||||
LOG.debug("Creating splits at " + jtFs.makeQualified(submitJobDir));
|
LOG.debug("Creating splits at " + jtFs.makeQualified(submitJobDir));
|
||||||
int maps = writeSplits(job, submitJobDir);
|
int maps = writeSplits(job, submitJobDir);
|
||||||
|
|
|
@ -46,15 +46,19 @@ public class AMStartedEvent implements HistoryEvent {
|
||||||
* the containerId of the AM.
|
* the containerId of the AM.
|
||||||
* @param nodeManagerHost
|
* @param nodeManagerHost
|
||||||
* the node on which the AM is running.
|
* the node on which the AM is running.
|
||||||
|
* @param nodeManagerPort
|
||||||
|
* the port on which the AM is running.
|
||||||
* @param nodeManagerHttpPort
|
* @param nodeManagerHttpPort
|
||||||
* the httpPort for the node running the AM.
|
* the httpPort for the node running the AM.
|
||||||
*/
|
*/
|
||||||
public AMStartedEvent(ApplicationAttemptId appAttemptId, long startTime,
|
public AMStartedEvent(ApplicationAttemptId appAttemptId, long startTime,
|
||||||
ContainerId containerId, String nodeManagerHost, int nodeManagerHttpPort) {
|
ContainerId containerId, String nodeManagerHost, int nodeManagerPort,
|
||||||
|
int nodeManagerHttpPort) {
|
||||||
datum.applicationAttemptId = new Utf8(appAttemptId.toString());
|
datum.applicationAttemptId = new Utf8(appAttemptId.toString());
|
||||||
datum.startTime = startTime;
|
datum.startTime = startTime;
|
||||||
datum.containerId = new Utf8(containerId.toString());
|
datum.containerId = new Utf8(containerId.toString());
|
||||||
datum.nodeManagerHost = new Utf8(nodeManagerHost);
|
datum.nodeManagerHost = new Utf8(nodeManagerHost);
|
||||||
|
datum.nodeManagerPort = nodeManagerPort;
|
||||||
datum.nodeManagerHttpPort = nodeManagerHttpPort;
|
datum.nodeManagerHttpPort = nodeManagerHttpPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,6 +102,13 @@ public class AMStartedEvent implements HistoryEvent {
|
||||||
return datum.nodeManagerHost.toString();
|
return datum.nodeManagerHost.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the node manager port.
|
||||||
|
*/
|
||||||
|
public int getNodeManagerPort() {
|
||||||
|
return datum.nodeManagerPort;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the http port for the tracker.
|
* @return the http port for the tracker.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -320,6 +320,7 @@ public class JobHistoryParser {
|
||||||
amInfo.startTime = event.getStartTime();
|
amInfo.startTime = event.getStartTime();
|
||||||
amInfo.containerId = event.getContainerId();
|
amInfo.containerId = event.getContainerId();
|
||||||
amInfo.nodeManagerHost = event.getNodeManagerHost();
|
amInfo.nodeManagerHost = event.getNodeManagerHost();
|
||||||
|
amInfo.nodeManagerPort = event.getNodeManagerPort();
|
||||||
amInfo.nodeManagerHttpPort = event.getNodeManagerHttpPort();
|
amInfo.nodeManagerHttpPort = event.getNodeManagerHttpPort();
|
||||||
if (info.amInfos == null) {
|
if (info.amInfos == null) {
|
||||||
info.amInfos = new LinkedList<AMInfo>();
|
info.amInfos = new LinkedList<AMInfo>();
|
||||||
|
@ -613,6 +614,7 @@ public class JobHistoryParser {
|
||||||
long startTime;
|
long startTime;
|
||||||
ContainerId containerId;
|
ContainerId containerId;
|
||||||
String nodeManagerHost;
|
String nodeManagerHost;
|
||||||
|
int nodeManagerPort;
|
||||||
int nodeManagerHttpPort;
|
int nodeManagerHttpPort;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -626,11 +628,13 @@ public class JobHistoryParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
public AMInfo(ApplicationAttemptId appAttemptId, long startTime,
|
public AMInfo(ApplicationAttemptId appAttemptId, long startTime,
|
||||||
ContainerId containerId, String nodeManagerHost, int nodeManagerHttpPort) {
|
ContainerId containerId, String nodeManagerHost, int nodeManagerPort,
|
||||||
|
int nodeManagerHttpPort) {
|
||||||
this.appAttemptId = appAttemptId;
|
this.appAttemptId = appAttemptId;
|
||||||
this.startTime = startTime;
|
this.startTime = startTime;
|
||||||
this.containerId = containerId;
|
this.containerId = containerId;
|
||||||
this.nodeManagerHost = nodeManagerHost;
|
this.nodeManagerHost = nodeManagerHost;
|
||||||
|
this.nodeManagerPort = nodeManagerPort;
|
||||||
this.nodeManagerHttpPort = nodeManagerHttpPort;
|
this.nodeManagerHttpPort = nodeManagerHttpPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -642,6 +646,7 @@ public class JobHistoryParser {
|
||||||
System.out.println("START_TIME: " + startTime);
|
System.out.println("START_TIME: " + startTime);
|
||||||
System.out.println("CONTAINER_ID: " + containerId.toString());
|
System.out.println("CONTAINER_ID: " + containerId.toString());
|
||||||
System.out.println("NODE_MANAGER_HOST: " + nodeManagerHost);
|
System.out.println("NODE_MANAGER_HOST: " + nodeManagerHost);
|
||||||
|
System.out.println("NODE_MANAGER_PORT: " + nodeManagerPort);
|
||||||
System.out.println("NODE_MANAGER_HTTP_PORT: " + nodeManagerHttpPort);
|
System.out.println("NODE_MANAGER_HTTP_PORT: " + nodeManagerHttpPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -665,6 +670,11 @@ public class JobHistoryParser {
|
||||||
return nodeManagerHost;
|
return nodeManagerHost;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return the port for the node manager running the AM */
|
||||||
|
public int getNodeManagerPort() {
|
||||||
|
return nodeManagerPort;
|
||||||
|
}
|
||||||
|
|
||||||
/** @return the http port for the node manager running the AM */
|
/** @return the http port for the node manager running the AM */
|
||||||
public int getNodeManagerHttpPort() {
|
public int getNodeManagerHttpPort() {
|
||||||
return nodeManagerHttpPort;
|
return nodeManagerHttpPort;
|
||||||
|
|
|
@ -66,12 +66,9 @@ import org.apache.hadoop.mapreduce.v2.app.job.Task;
|
||||||
import org.apache.hadoop.mapreduce.v2.app.security.authorize.MRAMPolicyProvider;
|
import org.apache.hadoop.mapreduce.v2.app.security.authorize.MRAMPolicyProvider;
|
||||||
import org.apache.hadoop.mapreduce.v2.hs.webapp.HsWebApp;
|
import org.apache.hadoop.mapreduce.v2.hs.webapp.HsWebApp;
|
||||||
import org.apache.hadoop.mapreduce.v2.jobhistory.JHAdminConfig;
|
import org.apache.hadoop.mapreduce.v2.jobhistory.JHAdminConfig;
|
||||||
import org.apache.hadoop.mapreduce.v2.security.client.ClientHSSecurityInfo;
|
|
||||||
import org.apache.hadoop.net.NetUtils;
|
import org.apache.hadoop.net.NetUtils;
|
||||||
import org.apache.hadoop.security.SecurityInfo;
|
|
||||||
import org.apache.hadoop.security.UserGroupInformation;
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
import org.apache.hadoop.yarn.YarnException;
|
import org.apache.hadoop.yarn.YarnException;
|
||||||
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
|
||||||
import org.apache.hadoop.yarn.exceptions.YarnRemoteException;
|
import org.apache.hadoop.yarn.exceptions.YarnRemoteException;
|
||||||
import org.apache.hadoop.yarn.factories.RecordFactory;
|
import org.apache.hadoop.yarn.factories.RecordFactory;
|
||||||
import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider;
|
import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider;
|
||||||
|
@ -144,7 +141,7 @@ public class HistoryClientService extends AbstractService {
|
||||||
webApp = new HsWebApp(history);
|
webApp = new HsWebApp(history);
|
||||||
String bindAddress = conf.get(JHAdminConfig.MR_HISTORY_WEBAPP_ADDRESS,
|
String bindAddress = conf.get(JHAdminConfig.MR_HISTORY_WEBAPP_ADDRESS,
|
||||||
JHAdminConfig.DEFAULT_MR_HISTORY_WEBAPP_ADDRESS);
|
JHAdminConfig.DEFAULT_MR_HISTORY_WEBAPP_ADDRESS);
|
||||||
WebApps.$for("jobhistory", this).at(bindAddress).start(webApp);
|
WebApps.$for("jobhistory", this).with(conf).at(bindAddress).start(webApp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -42,11 +42,6 @@ public class JobHistoryServer extends CompositeService {
|
||||||
private HistoryClientService clientService;
|
private HistoryClientService clientService;
|
||||||
private JobHistory jobHistoryService;
|
private JobHistory jobHistoryService;
|
||||||
|
|
||||||
static{
|
|
||||||
Configuration.addDefaultResource("mapred-default.xml");
|
|
||||||
Configuration.addDefaultResource("mapred-site.xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
public JobHistoryServer() {
|
public JobHistoryServer() {
|
||||||
super(JobHistoryServer.class.getName());
|
super(JobHistoryServer.class.getName());
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.io.IOException;
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
import org.apache.hadoop.mapreduce.v2.app.webapp.App;
|
import org.apache.hadoop.mapreduce.v2.app.webapp.App;
|
||||||
import org.apache.hadoop.mapreduce.v2.app.webapp.AppController;
|
import org.apache.hadoop.mapreduce.v2.app.webapp.AppController;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.webapp.AggregatedLogsPage;
|
||||||
import org.apache.hadoop.yarn.webapp.View;
|
import org.apache.hadoop.yarn.webapp.View;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
@ -32,6 +33,7 @@ import com.google.inject.Inject;
|
||||||
*/
|
*/
|
||||||
public class HsController extends AppController {
|
public class HsController extends AppController {
|
||||||
|
|
||||||
|
|
||||||
@Inject HsController(App app, Configuration conf, RequestContext ctx) {
|
@Inject HsController(App app, Configuration conf, RequestContext ctx) {
|
||||||
super(app, conf, ctx, "History");
|
super(app, conf, ctx, "History");
|
||||||
}
|
}
|
||||||
|
@ -169,6 +171,20 @@ public class HsController extends AppController {
|
||||||
render(aboutPage());
|
render(aboutPage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the logs page.
|
||||||
|
*/
|
||||||
|
public void logs() {
|
||||||
|
render(HsLogsPage.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the nm logs page.
|
||||||
|
*/
|
||||||
|
public void nmlogs() {
|
||||||
|
render(AggregatedLogsPage.class);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.apache.hadoop.mapreduce.v2.app.webapp.AppController#singleCounterPage()
|
* @see org.apache.hadoop.mapreduce.v2.app.webapp.AppController#singleCounterPage()
|
||||||
|
|
|
@ -24,6 +24,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.hadoop.mapreduce.JobACL;
|
import org.apache.hadoop.mapreduce.JobACL;
|
||||||
|
import org.apache.hadoop.mapreduce.jobhistory.JobHistoryParser.AMInfo;
|
||||||
import org.apache.hadoop.mapreduce.v2.api.records.JobId;
|
import org.apache.hadoop.mapreduce.v2.api.records.JobId;
|
||||||
import org.apache.hadoop.mapreduce.v2.api.records.JobReport;
|
import org.apache.hadoop.mapreduce.v2.api.records.JobReport;
|
||||||
import org.apache.hadoop.mapreduce.v2.api.records.TaskAttemptId;
|
import org.apache.hadoop.mapreduce.v2.api.records.TaskAttemptId;
|
||||||
|
@ -37,8 +38,13 @@ import org.apache.hadoop.mapreduce.v2.util.MRApps;
|
||||||
import org.apache.hadoop.mapreduce.v2.util.MRApps.TaskAttemptStateUI;
|
import org.apache.hadoop.mapreduce.v2.util.MRApps.TaskAttemptStateUI;
|
||||||
import org.apache.hadoop.security.authorize.AccessControlList;
|
import org.apache.hadoop.security.authorize.AccessControlList;
|
||||||
import org.apache.hadoop.util.StringUtils;
|
import org.apache.hadoop.util.StringUtils;
|
||||||
|
import org.apache.hadoop.yarn.api.records.NodeId;
|
||||||
|
import org.apache.hadoop.yarn.util.BuilderUtils;
|
||||||
import org.apache.hadoop.yarn.util.Times;
|
import org.apache.hadoop.yarn.util.Times;
|
||||||
import org.apache.hadoop.yarn.webapp.ResponseInfo;
|
import org.apache.hadoop.yarn.webapp.ResponseInfo;
|
||||||
|
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet;
|
||||||
|
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.DIV;
|
||||||
|
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.TABLE;
|
||||||
import org.apache.hadoop.yarn.webapp.view.HtmlBlock;
|
import org.apache.hadoop.yarn.webapp.view.HtmlBlock;
|
||||||
import org.apache.hadoop.yarn.webapp.view.InfoBlock;
|
import org.apache.hadoop.yarn.webapp.view.InfoBlock;
|
||||||
import static org.apache.hadoop.mapreduce.v2.app.webapp.AMWebApp.*;
|
import static org.apache.hadoop.mapreduce.v2.app.webapp.AMWebApp.*;
|
||||||
|
@ -86,7 +92,7 @@ public class HsJobBlock extends HtmlBlock {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Map<JobACL, AccessControlList> acls = job.getJobACLs();
|
Map<JobACL, AccessControlList> acls = job.getJobACLs();
|
||||||
|
List<AMInfo> amInfos = job.getAMInfos();
|
||||||
JobReport jobReport = job.getReport();
|
JobReport jobReport = job.getReport();
|
||||||
int mapTasks = job.getTotalMaps();
|
int mapTasks = job.getTotalMaps();
|
||||||
int mapTasksComplete = job.getCompletedMaps();
|
int mapTasksComplete = job.getCompletedMaps();
|
||||||
|
@ -105,6 +111,9 @@ public class HsJobBlock extends HtmlBlock {
|
||||||
_("Elapsed:", StringUtils.formatTime(
|
_("Elapsed:", StringUtils.formatTime(
|
||||||
Times.elapsed(startTime, finishTime, false)));
|
Times.elapsed(startTime, finishTime, false)));
|
||||||
|
|
||||||
|
String amString =
|
||||||
|
amInfos.size() == 1 ? "ApplicationMaster" : "ApplicationMasters";
|
||||||
|
|
||||||
List<String> diagnostics = job.getDiagnostics();
|
List<String> diagnostics = job.getDiagnostics();
|
||||||
if(diagnostics != null && !diagnostics.isEmpty()) {
|
if(diagnostics != null && !diagnostics.isEmpty()) {
|
||||||
StringBuffer b = new StringBuffer();
|
StringBuffer b = new StringBuffer();
|
||||||
|
@ -127,10 +136,44 @@ public class HsJobBlock extends HtmlBlock {
|
||||||
infoBlock._("ACL "+entry.getKey().getAclName()+":",
|
infoBlock._("ACL "+entry.getKey().getAclName()+":",
|
||||||
entry.getValue().getAclString());
|
entry.getValue().getAclString());
|
||||||
}
|
}
|
||||||
html.
|
DIV<Hamlet> div = html.
|
||||||
_(InfoBlock.class).
|
_(InfoBlock.class).
|
||||||
div(_INFO_WRAP).
|
div(_INFO_WRAP);
|
||||||
|
|
||||||
|
// MRAppMasters Table
|
||||||
|
TABLE<DIV<Hamlet>> table = div.table("#job");
|
||||||
|
table.
|
||||||
|
tr().
|
||||||
|
th(amString).
|
||||||
|
_().
|
||||||
|
tr().
|
||||||
|
th(_TH, "Attempt Number").
|
||||||
|
th(_TH, "Start Time").
|
||||||
|
th(_TH, "Node").
|
||||||
|
th(_TH, "Logs").
|
||||||
|
_();
|
||||||
|
for (AMInfo amInfo : amInfos) {
|
||||||
|
String nodeHttpAddress = amInfo.getNodeManagerHost() +
|
||||||
|
":" + amInfo.getNodeManagerHttpPort();
|
||||||
|
NodeId nodeId = BuilderUtils.newNodeId(
|
||||||
|
amInfo.getNodeManagerHost(), amInfo.getNodeManagerPort());
|
||||||
|
|
||||||
|
table.tr().
|
||||||
|
td(String.valueOf(amInfo.getAppAttemptId().getAttemptId())).
|
||||||
|
td(new Date(amInfo.getStartTime()).toString()).
|
||||||
|
td().a(".nodelink", url("http://", nodeHttpAddress),
|
||||||
|
nodeHttpAddress)._().
|
||||||
|
td().a(".logslink", url("logs", nodeId.toString(),
|
||||||
|
amInfo.getContainerId().toString(), jid, job.getUserName()),
|
||||||
|
"logs")._().
|
||||||
|
_();
|
||||||
|
}
|
||||||
|
table._();
|
||||||
|
div._();
|
||||||
|
|
||||||
|
|
||||||
|
html.div(_INFO_WRAP).
|
||||||
|
|
||||||
// Tasks table
|
// Tasks table
|
||||||
table("#job").
|
table("#job").
|
||||||
tr().
|
tr().
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.apache.hadoop.mapreduce.v2.hs.webapp;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.yarn.server.nodemanager.webapp.NMWebParams.CONTAINER_ID;
|
||||||
|
import static org.apache.hadoop.yarn.server.nodemanager.webapp.NMWebParams.ENTITY_STRING;
|
||||||
|
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.webapp.AggregatedLogsBlock;
|
||||||
|
import org.apache.hadoop.yarn.webapp.SubView;
|
||||||
|
|
||||||
|
public class HsLogsPage extends HsView {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.apache.hadoop.mapreduce.v2.hs.webapp.HsView#preHead(org.apache.hadoop.yarn.webapp.hamlet.Hamlet.HTML)
|
||||||
|
*/
|
||||||
|
@Override protected void preHead(Page.HTML<_> html) {
|
||||||
|
String logEntity = $(ENTITY_STRING);
|
||||||
|
if (logEntity == null || logEntity.isEmpty()) {
|
||||||
|
logEntity = $(CONTAINER_ID);
|
||||||
|
}
|
||||||
|
if (logEntity == null || logEntity.isEmpty()) {
|
||||||
|
logEntity = "UNKNOWN";
|
||||||
|
}
|
||||||
|
commonPreHead(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The content of this page is the JobBlock
|
||||||
|
* @return HsJobBlock.class
|
||||||
|
*/
|
||||||
|
@Override protected Class<? extends SubView> content() {
|
||||||
|
return AggregatedLogsBlock.class;
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ import org.apache.hadoop.yarn.webapp.SubView;
|
||||||
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet;
|
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet;
|
||||||
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.TABLE;
|
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.TABLE;
|
||||||
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.TBODY;
|
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.TBODY;
|
||||||
|
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.TD;
|
||||||
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.TFOOT;
|
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.TFOOT;
|
||||||
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.THEAD;
|
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.THEAD;
|
||||||
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.TR;
|
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.TR;
|
||||||
|
@ -111,6 +112,8 @@ public class HsTaskPage extends HsView {
|
||||||
String taid = MRApps.toString(ta.getID());
|
String taid = MRApps.toString(ta.getID());
|
||||||
|
|
||||||
String nodeHttpAddr = ta.getNodeHttpAddress();
|
String nodeHttpAddr = ta.getNodeHttpAddress();
|
||||||
|
String containerIdString = ta.getAssignedContainerID().toString();
|
||||||
|
String nodeIdString = ta.getAssignedContainerMgrAddress();
|
||||||
|
|
||||||
long attemptStartTime = ta.getLaunchTime();
|
long attemptStartTime = ta.getLaunchTime();
|
||||||
long shuffleFinishTime = -1;
|
long shuffleFinishTime = -1;
|
||||||
|
@ -134,12 +137,16 @@ public class HsTaskPage extends HsView {
|
||||||
int sortId = ta.getID().getId() + (ta.getID().getTaskId().getId() * 10000);
|
int sortId = ta.getID().getId() + (ta.getID().getTaskId().getId() * 10000);
|
||||||
|
|
||||||
TR<TBODY<TABLE<Hamlet>>> row = tbody.tr();
|
TR<TBODY<TABLE<Hamlet>>> row = tbody.tr();
|
||||||
row.
|
TD<TR<TBODY<TABLE<Hamlet>>>> td = row.td();
|
||||||
td().
|
|
||||||
br().$title(String.valueOf(sortId))._(). // sorting
|
td.br().$title(String.valueOf(sortId))._()
|
||||||
_(taid)._().
|
. // sorting
|
||||||
td(ta.getState().toString()).
|
_(taid)._().td(ta.getState().toString()).td()
|
||||||
td().a(".nodelink", url("http://", nodeHttpAddr), nodeHttpAddr)._();
|
.a(".nodelink", url("http://", nodeHttpAddr), nodeHttpAddr);
|
||||||
|
td._(" ").a(".logslink",
|
||||||
|
url("logs", nodeIdString, containerIdString, taid, app.getJob()
|
||||||
|
.getUserName()), "logs");
|
||||||
|
td._();
|
||||||
|
|
||||||
row.td().
|
row.td().
|
||||||
br().$title(String.valueOf(attemptStartTime))._().
|
br().$title(String.valueOf(attemptStartTime))._().
|
||||||
|
|
|
@ -18,6 +18,10 @@
|
||||||
|
|
||||||
package org.apache.hadoop.mapreduce.v2.hs.webapp;
|
package org.apache.hadoop.mapreduce.v2.hs.webapp;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.yarn.server.nodemanager.webapp.NMWebParams.CONTAINER_ID;
|
||||||
|
import static org.apache.hadoop.yarn.server.nodemanager.webapp.NMWebParams.NM_NODENAME;
|
||||||
|
import static org.apache.hadoop.yarn.server.nodemanager.webapp.NMWebParams.ENTITY_STRING;
|
||||||
|
import static org.apache.hadoop.yarn.server.nodemanager.webapp.NMWebParams.APP_OWNER;
|
||||||
import static org.apache.hadoop.yarn.util.StringHelper.pajoin;
|
import static org.apache.hadoop.yarn.util.StringHelper.pajoin;
|
||||||
|
|
||||||
import org.apache.hadoop.mapreduce.v2.app.AppContext;
|
import org.apache.hadoop.mapreduce.v2.app.AppContext;
|
||||||
|
@ -51,6 +55,10 @@ public class HsWebApp extends WebApp implements AMParams {
|
||||||
route(pajoin("/singletaskcounter",TASK_ID, COUNTER_GROUP, COUNTER_NAME),
|
route(pajoin("/singletaskcounter",TASK_ID, COUNTER_GROUP, COUNTER_NAME),
|
||||||
HsController.class, "singleTaskCounter");
|
HsController.class, "singleTaskCounter");
|
||||||
route("/about", HsController.class, "about");
|
route("/about", HsController.class, "about");
|
||||||
|
route(pajoin("/logs", NM_NODENAME, CONTAINER_ID, ENTITY_STRING, APP_OWNER),
|
||||||
|
HsController.class, "logs");
|
||||||
|
route(pajoin("/nmlogs", NM_NODENAME, CONTAINER_ID, ENTITY_STRING, APP_OWNER),
|
||||||
|
HsController.class, "nmlogs");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -115,6 +115,7 @@ public class TestJobHistoryParsing {
|
||||||
Assert.assertEquals("testhost", jobInfo.getAMInfos().get(0)
|
Assert.assertEquals("testhost", jobInfo.getAMInfos().get(0)
|
||||||
.getNodeManagerHost());
|
.getNodeManagerHost());
|
||||||
AMInfo amInfo = jobInfo.getAMInfos().get(0);
|
AMInfo amInfo = jobInfo.getAMInfos().get(0);
|
||||||
|
Assert.assertEquals(2222, amInfo.getNodeManagerPort());
|
||||||
Assert.assertEquals(3333, amInfo.getNodeManagerHttpPort());
|
Assert.assertEquals(3333, amInfo.getNodeManagerHttpPort());
|
||||||
Assert.assertEquals(1, amInfo.getAppAttemptId().getAttemptId());
|
Assert.assertEquals(1, amInfo.getAppAttemptId().getAttemptId());
|
||||||
Assert.assertEquals(amInfo.getAppAttemptId(), amInfo.getContainerId()
|
Assert.assertEquals(amInfo.getAppAttemptId(), amInfo.getContainerId()
|
||||||
|
|
|
@ -22,8 +22,14 @@ import static org.apache.hadoop.mapreduce.v2.app.webapp.AMParams.APP_ID;
|
||||||
import static org.apache.hadoop.mapreduce.v2.app.webapp.AMParams.ATTEMPT_STATE;
|
import static org.apache.hadoop.mapreduce.v2.app.webapp.AMParams.ATTEMPT_STATE;
|
||||||
import static org.apache.hadoop.mapreduce.v2.app.webapp.AMParams.JOB_ID;
|
import static org.apache.hadoop.mapreduce.v2.app.webapp.AMParams.JOB_ID;
|
||||||
import static org.apache.hadoop.mapreduce.v2.app.webapp.AMParams.TASK_TYPE;
|
import static org.apache.hadoop.mapreduce.v2.app.webapp.AMParams.TASK_TYPE;
|
||||||
|
import static org.apache.hadoop.yarn.server.nodemanager.webapp.NMWebParams.CONTAINER_ID;
|
||||||
|
import static org.apache.hadoop.yarn.server.nodemanager.webapp.NMWebParams.NM_NODENAME;
|
||||||
|
import static org.apache.hadoop.yarn.server.nodemanager.webapp.NMWebParams.ENTITY_STRING;
|
||||||
|
import static org.apache.hadoop.yarn.server.nodemanager.webapp.NMWebParams.APP_OWNER;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -38,9 +44,12 @@ import org.apache.hadoop.yarn.Clock;
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
|
import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
||||||
import org.apache.hadoop.yarn.event.EventHandler;
|
import org.apache.hadoop.yarn.event.EventHandler;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.webapp.AggregatedLogsPage;
|
||||||
|
import org.apache.hadoop.yarn.util.BuilderUtils;
|
||||||
import org.apache.hadoop.yarn.webapp.test.WebAppTests;
|
import org.apache.hadoop.yarn.webapp.test.WebAppTests;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
|
|
||||||
public class TestHSWebApp {
|
public class TestHSWebApp {
|
||||||
|
@ -188,4 +197,40 @@ public class TestHSWebApp {
|
||||||
WebAppTests.testPage(HsSingleCounterPage.class, AppContext.class,
|
WebAppTests.testPage(HsSingleCounterPage.class, AppContext.class,
|
||||||
new TestAppContext());
|
new TestAppContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLogsView1() throws IOException {
|
||||||
|
LOG.info("HsLogsPage");
|
||||||
|
Injector injector =
|
||||||
|
WebAppTests.testPage(AggregatedLogsPage.class, AppContext.class,
|
||||||
|
new TestAppContext());
|
||||||
|
PrintWriter spyPw = WebAppTests.getPrintWriter(injector);
|
||||||
|
verify(spyPw).write("Cannot get container logs without a ContainerId");
|
||||||
|
verify(spyPw).write("Cannot get container logs without a NodeId");
|
||||||
|
verify(spyPw).write("Cannot get container logs without an app owner");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLogsView2() throws IOException {
|
||||||
|
LOG.info("HsLogsPage with data");
|
||||||
|
TestAppContext ctx = new TestAppContext();
|
||||||
|
Map<String, String> params = new HashMap<String, String>();
|
||||||
|
|
||||||
|
params.put(CONTAINER_ID, BuilderUtils.newContainerId(1, 1, 333, 1)
|
||||||
|
.toString());
|
||||||
|
params.put(NM_NODENAME, BuilderUtils.newNodeId("testhost", 2222).toString());
|
||||||
|
params.put(ENTITY_STRING, "container_10_0001_01_000001");
|
||||||
|
params.put(APP_OWNER, "owner");
|
||||||
|
|
||||||
|
Injector injector =
|
||||||
|
WebAppTests.testPage(AggregatedLogsPage.class, AppContext.class, ctx,
|
||||||
|
params);
|
||||||
|
PrintWriter spyPw = WebAppTests.getPrintWriter(injector);
|
||||||
|
verify(spyPw).write(
|
||||||
|
"Logs not available for container_10_0001_01_000001. Aggregation "
|
||||||
|
+ "may not be complete,"
|
||||||
|
+ " Check back later or try the nodemanager on testhost:2222");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -119,6 +119,7 @@ public class MiniMRYarnCluster extends MiniYARNCluster {
|
||||||
LOG.info("Waiting for HistoryServer to start...");
|
LOG.info("Waiting for HistoryServer to start...");
|
||||||
Thread.sleep(1500);
|
Thread.sleep(1500);
|
||||||
}
|
}
|
||||||
|
//TODO Add a timeout. State.STOPPED check ?
|
||||||
if (historyServer.getServiceState() != STATE.STARTED) {
|
if (historyServer.getServiceState() != STATE.STARTED) {
|
||||||
throw new IOException("HistoryServer failed to start");
|
throw new IOException("HistoryServer failed to start");
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,11 @@
|
||||||
<Method name="handle" />
|
<Method name="handle" />
|
||||||
<Bug pattern="BC_UNCONFIRMED_CAST" />
|
<Bug pattern="BC_UNCONFIRMED_CAST" />
|
||||||
</Match>
|
</Match>
|
||||||
|
<Match>
|
||||||
|
<Class name="~org\.apache\.hadoop\.yarn\.server\.nodemanager\.containermanager\.logaggregation\.LogAggregationService.*" />
|
||||||
|
<Method name="handle" />
|
||||||
|
<Bug pattern="BC_UNCONFIRMED_CAST" />
|
||||||
|
</Match>
|
||||||
|
|
||||||
<!-- Ignore intentional switch fallthroughs -->
|
<!-- Ignore intentional switch fallthroughs -->
|
||||||
<Match>
|
<Match>
|
||||||
|
|
|
@ -42,12 +42,24 @@ public interface ApplicationConstants {
|
||||||
* only
|
* only
|
||||||
*/
|
*/
|
||||||
public static final String AM_CONTAINER_ID_ENV = "AM_CONTAINER_ID";
|
public static final String AM_CONTAINER_ID_ENV = "AM_CONTAINER_ID";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The environment variable for NM_HTTP_ADDRESS. Set in AppMaster environment
|
* The environment variable for the NM_HOST. Set in the AppMaster environment
|
||||||
* only
|
* only
|
||||||
*/
|
*/
|
||||||
public static final String NM_HTTP_ADDRESS_ENV = "NM_HTTP_ADDRESS";
|
public static final String NM_HOST_ENV = "NM_HOST";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The environment variable for the NM_PORT. Set in the AppMaster environment
|
||||||
|
* only
|
||||||
|
*/
|
||||||
|
public static final String NM_PORT_ENV = "NM_PORT";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The environment variable for the NM_HTTP_PORT. Set in the AppMaster environment
|
||||||
|
* only
|
||||||
|
*/
|
||||||
|
public static final String NM_HTTP_PORT_ENV = "NM_HTTP_PORT";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The environment variable for APP_SUBMIT_TIME. Set in AppMaster environment
|
* The environment variable for APP_SUBMIT_TIME. Set in AppMaster environment
|
||||||
|
|
|
@ -137,6 +137,9 @@ public class YarnConfiguration extends Configuration {
|
||||||
YARN_PREFIX + "admin.acl";
|
YARN_PREFIX + "admin.acl";
|
||||||
public static final String DEFAULT_YARN_ADMIN_ACL = "*";
|
public static final String DEFAULT_YARN_ADMIN_ACL = "*";
|
||||||
|
|
||||||
|
/** ACL used in case none is found. Allows nothing. */
|
||||||
|
public static final String DEFAULT_YARN_APP_ACL = " ";
|
||||||
|
|
||||||
/** The address of the RM admin interface.*/
|
/** The address of the RM admin interface.*/
|
||||||
public static final String RM_ADMIN_ADDRESS =
|
public static final String RM_ADMIN_ADDRESS =
|
||||||
RM_PREFIX + "admin.address";
|
RM_PREFIX + "admin.address";
|
||||||
|
@ -290,10 +293,25 @@ public class YarnConfiguration extends Configuration {
|
||||||
public static final String NM_LOG_DIRS = NM_PREFIX + "log-dirs";
|
public static final String NM_LOG_DIRS = NM_PREFIX + "log-dirs";
|
||||||
public static final String DEFAULT_NM_LOG_DIRS = "/tmp/logs";
|
public static final String DEFAULT_NM_LOG_DIRS = "/tmp/logs";
|
||||||
|
|
||||||
|
/** Whether to enable log aggregation */
|
||||||
|
public static final String NM_LOG_AGGREGATION_ENABLED = NM_PREFIX
|
||||||
|
+ "log-aggregation.enable";
|
||||||
|
|
||||||
/** Where to aggregate logs to.*/
|
/** Where to aggregate logs to.*/
|
||||||
public static final String NM_REMOTE_APP_LOG_DIR =
|
public static final String NM_REMOTE_APP_LOG_DIR =
|
||||||
NM_PREFIX + "remote-app-log-dir";
|
NM_PREFIX + "remote-app-log-dir";
|
||||||
public static final String DEFAULT_NM_REMOTE_APP_LOG_DIR = "/tmp/logs";
|
public static final String DEFAULT_NM_REMOTE_APP_LOG_DIR = "/tmp/logs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The remote log dir will be created at
|
||||||
|
* NM_REMOTE_APP_LOG_DIR/${user}/NM_REMOTE_APP_LOG_DIR_SUFFIX/${appId}
|
||||||
|
*/
|
||||||
|
public static final String NM_REMOTE_APP_LOG_DIR_SUFFIX =
|
||||||
|
NM_PREFIX + "remote-app-log-dir-suffix";
|
||||||
|
public static final String DEFAULT_NM_REMOTE_APP_LOG_DIR_SUFFIX="logs";
|
||||||
|
|
||||||
|
public static final String YARN_LOG_SERVER_URL =
|
||||||
|
YARN_PREFIX + "log.server.url";
|
||||||
|
|
||||||
/** Amount of memory in GB that can be allocated for containers.*/
|
/** Amount of memory in GB that can be allocated for containers.*/
|
||||||
public static final String NM_PMEM_MB = NM_PREFIX + "resource.memory-mb";
|
public static final String NM_PMEM_MB = NM_PREFIX + "resource.memory-mb";
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.apache.hadoop.fs.Path;
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
|
import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
||||||
import org.apache.hadoop.yarn.api.records.ContainerId;
|
import org.apache.hadoop.yarn.api.records.ContainerId;
|
||||||
|
import org.apache.hadoop.yarn.api.records.NodeId;
|
||||||
import org.apache.hadoop.yarn.api.records.URL;
|
import org.apache.hadoop.yarn.api.records.URL;
|
||||||
import org.apache.hadoop.yarn.factories.RecordFactory;
|
import org.apache.hadoop.yarn.factories.RecordFactory;
|
||||||
import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider;
|
import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider;
|
||||||
|
@ -129,6 +130,21 @@ public class ConverterUtils {
|
||||||
return cId.toString();
|
return cId.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static NodeId toNodeId(String nodeIdStr) {
|
||||||
|
String[] parts = nodeIdStr.split(":");
|
||||||
|
if (parts.length != 2) {
|
||||||
|
throw new IllegalArgumentException("Invalid NodeId [" + nodeIdStr
|
||||||
|
+ "]. Expected host:port");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
NodeId nodeId =
|
||||||
|
BuilderUtils.newNodeId(parts[0], Integer.parseInt(parts[1]));
|
||||||
|
return nodeId;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("Invalid port: " + parts[1], e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static ContainerId toContainerId(String containerIdStr) {
|
public static ContainerId toContainerId(String containerIdStr) {
|
||||||
Iterator<String> it = _split(containerIdStr).iterator();
|
Iterator<String> it = _split(containerIdStr).iterator();
|
||||||
if (!it.next().equals(CONTAINER_PREFIX)) {
|
if (!it.next().equals(CONTAINER_PREFIX)) {
|
||||||
|
|
|
@ -28,4 +28,4 @@ public interface Params {
|
||||||
static final String TITLE_LINK = "title.href";
|
static final String TITLE_LINK = "title.href";
|
||||||
static final String USER = "user";
|
static final String USER = "user";
|
||||||
static final String ERROR_DETAILS = "error.details";
|
static final String ERROR_DETAILS = "error.details";
|
||||||
}
|
}
|
|
@ -34,7 +34,9 @@ import com.google.inject.Guice;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
import com.google.inject.Provides;
|
import com.google.inject.Provides;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
@ -169,4 +171,16 @@ public class WebAppTests {
|
||||||
public static <T> Injector testBlock(Class<? extends SubView> block) {
|
public static <T> Injector testBlock(Class<? extends SubView> block) {
|
||||||
return testBlock(block, null, null);
|
return testBlock(block, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to get the spy writer.
|
||||||
|
* @param injector the injector used for the test.
|
||||||
|
* @return The Spy writer.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static PrintWriter getPrintWriter(Injector injector)
|
||||||
|
throws IOException {
|
||||||
|
HttpServletResponse res = injector.getInstance(HttpServletResponse.class);
|
||||||
|
return res.getWriter();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.apache.hadoop.security.UserGroupInformation;
|
||||||
import org.apache.hadoop.security.authorize.AccessControlList;
|
import org.apache.hadoop.security.authorize.AccessControlList;
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationAccessType;
|
import org.apache.hadoop.yarn.api.records.ApplicationAccessType;
|
||||||
|
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
||||||
import org.apache.hadoop.yarn.security.AdminACLsManager;
|
import org.apache.hadoop.yarn.security.AdminACLsManager;
|
||||||
|
|
||||||
@InterfaceAudience.Private
|
@InterfaceAudience.Private
|
||||||
|
@ -47,7 +48,7 @@ public class ApplicationACLsManager {
|
||||||
|
|
||||||
public ApplicationACLsManager(Configuration conf) {
|
public ApplicationACLsManager(Configuration conf) {
|
||||||
this.conf = conf;
|
this.conf = conf;
|
||||||
this.adminAclsManager = new AdminACLsManager(conf);
|
this.adminAclsManager = new AdminACLsManager(this.conf);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean areACLsEnabled() {
|
public boolean areACLsEnabled() {
|
||||||
|
@ -102,6 +103,16 @@ public class ApplicationACLsManager {
|
||||||
|
|
||||||
AccessControlList applicationACL = this.applicationACLS
|
AccessControlList applicationACL = this.applicationACLS
|
||||||
.get(applicationId).get(applicationAccessType);
|
.get(applicationId).get(applicationAccessType);
|
||||||
|
if (applicationACL == null) {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("ACL not found for access-type " + applicationAccessType
|
||||||
|
+ " for application " + applicationId + " owned by "
|
||||||
|
+ applicationOwner + ". Using default ["
|
||||||
|
+ YarnConfiguration.DEFAULT_YARN_APP_ACL + "]");
|
||||||
|
}
|
||||||
|
applicationACL =
|
||||||
|
new AccessControlList(YarnConfiguration.DEFAULT_YARN_APP_ACL);
|
||||||
|
}
|
||||||
|
|
||||||
// Allow application-owner for any type of access on the application
|
// Allow application-owner for any type of access on the application
|
||||||
if (this.adminAclsManager.isAdmin(callerUGI)
|
if (this.adminAclsManager.isAdmin(callerUGI)
|
||||||
|
|
|
@ -279,11 +279,24 @@
|
||||||
<value>/tmp/logs</value>
|
<value>/tmp/logs</value>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
|
<property>
|
||||||
|
<description>Whether to enable log aggregation</description>
|
||||||
|
<name>yarn.nodemanager.log-aggregation.enable</name>
|
||||||
|
<value>false</value>
|
||||||
|
</property>
|
||||||
<property>
|
<property>
|
||||||
<description>Where to aggregate logs to.</description>
|
<description>Where to aggregate logs to.</description>
|
||||||
<name>yarn.nodemanager.remote-app-log-dir</name>
|
<name>yarn.nodemanager.remote-app-log-dir</name>
|
||||||
<value>/tmp/logs</value>
|
<value>/tmp/logs</value>
|
||||||
</property>
|
</property>
|
||||||
|
<property>
|
||||||
|
<description>The remote log dir will be created at
|
||||||
|
{yarn.nodemanager.remote-app-log-dir}/${user}/{thisParam}
|
||||||
|
</description>
|
||||||
|
<name>yarn.nodemanager.remote-app-log-dir-suffix</name>
|
||||||
|
<value>logs</value>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
|
||||||
<property>
|
<property>
|
||||||
<description>Amount of physical memory, in MB, that can be allocated
|
<description>Amount of physical memory, in MB, that can be allocated
|
||||||
|
|
|
@ -20,5 +20,5 @@ package org.apache.hadoop.yarn.server.nodemanager;
|
||||||
|
|
||||||
public enum ContainerManagerEventType {
|
public enum ContainerManagerEventType {
|
||||||
FINISH_APPS,
|
FINISH_APPS,
|
||||||
FINISH_CONTAINERS
|
FINISH_CONTAINERS,
|
||||||
}
|
}
|
||||||
|
|
|
@ -266,6 +266,8 @@ public class NodeStatusUpdaterImpl extends AbstractService implements
|
||||||
new CMgrCompletedAppsEvent(appsToCleanup));
|
new CMgrCompletedAppsEvent(appsToCleanup));
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
// TODO Better error handling. Thread can die with the rest of the
|
||||||
|
// NM still running.
|
||||||
LOG.error("Caught exception in status-updater", e);
|
LOG.error("Caught exception in status-updater", e);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,7 +168,7 @@ public class ContainerManagerImpl extends CompositeService implements
|
||||||
|
|
||||||
protected LogAggregationService createLogAggregationService(Context context,
|
protected LogAggregationService createLogAggregationService(Context context,
|
||||||
DeletionService deletionService) {
|
DeletionService deletionService) {
|
||||||
return new LogAggregationService(context, deletionService);
|
return new LogAggregationService(this.dispatcher, context, deletionService);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContainersMonitor getContainersMonitor() {
|
public ContainersMonitor getContainersMonitor() {
|
||||||
|
@ -289,8 +289,9 @@ public class ContainerManagerImpl extends CompositeService implements
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the application
|
// Create the application
|
||||||
Application application = new ApplicationImpl(dispatcher,
|
Application application =
|
||||||
this.aclsManager, launchContext.getUser(), applicationID, credentials);
|
new ApplicationImpl(dispatcher, this.aclsManager,
|
||||||
|
launchContext.getUser(), applicationID, credentials, context);
|
||||||
if (null ==
|
if (null ==
|
||||||
context.getApplications().putIfAbsent(applicationID, application)) {
|
context.getApplications().putIfAbsent(applicationID, application)) {
|
||||||
LOG.info("Creating a new application reference for app "
|
LOG.info("Creating a new application reference for app "
|
||||||
|
@ -319,6 +320,7 @@ public class ContainerManagerImpl extends CompositeService implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public StopContainerResponse stopContainer(StopContainerRequest request)
|
public StopContainerResponse stopContainer(StopContainerRequest request)
|
||||||
throws YarnRemoteException {
|
throws YarnRemoteException {
|
||||||
|
|
||||||
|
@ -398,20 +400,20 @@ public class ContainerManagerImpl extends CompositeService implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(ApplicationEvent event) {
|
public void handle(ApplicationEvent event) {
|
||||||
Application app =
|
Application app =
|
||||||
ContainerManagerImpl.this.context.getApplications().get(
|
ContainerManagerImpl.this.context.getApplications().get(
|
||||||
event.getApplicationID());
|
event.getApplicationID());
|
||||||
if (app != null) {
|
if (app != null) {
|
||||||
app.handle(event);
|
app.handle(event);
|
||||||
} else {
|
} else {
|
||||||
LOG.warn("Event " + event + " sent to absent application " +
|
LOG.warn("Event " + event + " sent to absent application "
|
||||||
event.getApplicationID());
|
+ event.getApplicationID());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public void handle(ContainerManagerEvent event) {
|
public void handle(ContainerManagerEvent event) {
|
||||||
switch (event.getType()) {
|
switch (event.getType()) {
|
||||||
case FINISH_APPS:
|
case FINISH_APPS:
|
||||||
|
|
|
@ -20,7 +20,6 @@ package org.apache.hadoop.yarn.server.nodemanager.containermanager.application;
|
||||||
|
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
||||||
import org.apache.hadoop.yarn.event.AbstractEvent;
|
import org.apache.hadoop.yarn.event.AbstractEvent;
|
||||||
import org.apache.hadoop.yarn.event.Event;
|
|
||||||
|
|
||||||
public class ApplicationEvent extends AbstractEvent<ApplicationEventType> {
|
public class ApplicationEvent extends AbstractEvent<ApplicationEventType> {
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,6 @@ public enum ApplicationEventType {
|
||||||
// Source: Container
|
// Source: Container
|
||||||
APPLICATION_CONTAINER_FINISHED,
|
APPLICATION_CONTAINER_FINISHED,
|
||||||
|
|
||||||
// Source: LogAggregationService.
|
// Source: Log Aggregation
|
||||||
APPLICATION_FINISHED,
|
APPLICATION_LOG_AGGREGATION_FINISHED
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,10 @@ import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.apache.hadoop.security.Credentials;
|
import org.apache.hadoop.security.Credentials;
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
||||||
|
import org.apache.hadoop.yarn.api.records.ApplicationAccessType;
|
||||||
import org.apache.hadoop.yarn.api.records.ContainerId;
|
import org.apache.hadoop.yarn.api.records.ContainerId;
|
||||||
import org.apache.hadoop.yarn.event.Dispatcher;
|
import org.apache.hadoop.yarn.event.Dispatcher;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.Context;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.AuxServicesEvent;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.AuxServicesEvent;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.AuxServicesEventType;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.AuxServicesEventType;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.Container;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.Container;
|
||||||
|
@ -48,7 +50,6 @@ import org.apache.hadoop.yarn.state.MultipleArcTransition;
|
||||||
import org.apache.hadoop.yarn.state.SingleArcTransition;
|
import org.apache.hadoop.yarn.state.SingleArcTransition;
|
||||||
import org.apache.hadoop.yarn.state.StateMachine;
|
import org.apache.hadoop.yarn.state.StateMachine;
|
||||||
import org.apache.hadoop.yarn.state.StateMachineFactory;
|
import org.apache.hadoop.yarn.state.StateMachineFactory;
|
||||||
import org.apache.hadoop.yarn.util.ConverterUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The state machine for the representation of an Application
|
* The state machine for the representation of an Application
|
||||||
|
@ -63,6 +64,7 @@ public class ApplicationImpl implements Application {
|
||||||
final ApplicationACLsManager aclsManager;
|
final ApplicationACLsManager aclsManager;
|
||||||
private final ReadLock readLock;
|
private final ReadLock readLock;
|
||||||
private final WriteLock writeLock;
|
private final WriteLock writeLock;
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
private static final Log LOG = LogFactory.getLog(Application.class);
|
private static final Log LOG = LogFactory.getLog(Application.class);
|
||||||
|
|
||||||
|
@ -71,12 +73,13 @@ public class ApplicationImpl implements Application {
|
||||||
|
|
||||||
public ApplicationImpl(Dispatcher dispatcher,
|
public ApplicationImpl(Dispatcher dispatcher,
|
||||||
ApplicationACLsManager aclsManager, String user, ApplicationId appId,
|
ApplicationACLsManager aclsManager, String user, ApplicationId appId,
|
||||||
Credentials credentials) {
|
Credentials credentials, Context context) {
|
||||||
this.dispatcher = dispatcher;
|
this.dispatcher = dispatcher;
|
||||||
this.user = user.toString();
|
this.user = user.toString();
|
||||||
this.appId = appId;
|
this.appId = appId;
|
||||||
this.credentials = credentials;
|
this.credentials = credentials;
|
||||||
this.aclsManager = aclsManager;
|
this.aclsManager = aclsManager;
|
||||||
|
this.context = context;
|
||||||
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||||
readLock = lock.readLock();
|
readLock = lock.readLock();
|
||||||
writeLock = lock.writeLock();
|
writeLock = lock.writeLock();
|
||||||
|
@ -173,7 +176,13 @@ public class ApplicationImpl implements Application {
|
||||||
ApplicationState.FINISHED,
|
ApplicationState.FINISHED,
|
||||||
ApplicationEventType.APPLICATION_RESOURCES_CLEANEDUP,
|
ApplicationEventType.APPLICATION_RESOURCES_CLEANEDUP,
|
||||||
new AppCompletelyDoneTransition())
|
new AppCompletelyDoneTransition())
|
||||||
|
|
||||||
|
// Transitions from FINISHED state
|
||||||
|
.addTransition(ApplicationState.FINISHED,
|
||||||
|
ApplicationState.FINISHED,
|
||||||
|
ApplicationEventType.APPLICATION_LOG_AGGREGATION_FINISHED,
|
||||||
|
new AppLogsAggregatedTransition())
|
||||||
|
|
||||||
// create the topology tables
|
// create the topology tables
|
||||||
.installTopology();
|
.installTopology();
|
||||||
|
|
||||||
|
@ -239,11 +248,15 @@ public class ApplicationImpl implements Application {
|
||||||
@Override
|
@Override
|
||||||
public void transition(ApplicationImpl app, ApplicationEvent event) {
|
public void transition(ApplicationImpl app, ApplicationEvent event) {
|
||||||
|
|
||||||
|
Map<ApplicationAccessType, String> appAcls =
|
||||||
|
app.getContainers().values().iterator().next().getLaunchContext()
|
||||||
|
.getApplicationACLs();
|
||||||
|
|
||||||
// Inform the logAggregator
|
// Inform the logAggregator
|
||||||
app.dispatcher.getEventHandler().handle(
|
app.dispatcher.getEventHandler().handle(
|
||||||
new LogAggregatorAppStartedEvent(app.appId, app.user,
|
new LogAggregatorAppStartedEvent(app.appId, app.user,
|
||||||
app.credentials,
|
app.credentials, ContainerLogsRetentionPolicy.ALL_CONTAINERS,
|
||||||
ContainerLogsRetentionPolicy.ALL_CONTAINERS)); // TODO: Fix
|
appAcls));
|
||||||
|
|
||||||
// Start all the containers waiting for ApplicationInit
|
// Start all the containers waiting for ApplicationInit
|
||||||
for (Container container : app.containers.values()) {
|
for (Container container : app.containers.values()) {
|
||||||
|
@ -339,13 +352,20 @@ public class ApplicationImpl implements Application {
|
||||||
@Override
|
@Override
|
||||||
public void transition(ApplicationImpl app, ApplicationEvent event) {
|
public void transition(ApplicationImpl app, ApplicationEvent event) {
|
||||||
|
|
||||||
app.aclsManager.removeApplication(app.getAppId());
|
|
||||||
|
|
||||||
// Inform the logService
|
// Inform the logService
|
||||||
app.dispatcher.getEventHandler().handle(
|
app.dispatcher.getEventHandler().handle(
|
||||||
new LogAggregatorAppFinishedEvent(app.appId));
|
new LogAggregatorAppFinishedEvent(app.appId));
|
||||||
|
|
||||||
// TODO: Also make logService write the acls to the aggregated file.
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class AppLogsAggregatedTransition implements
|
||||||
|
SingleArcTransition<ApplicationImpl, ApplicationEvent> {
|
||||||
|
@Override
|
||||||
|
public void transition(ApplicationImpl app, ApplicationEvent event) {
|
||||||
|
ApplicationId appId = event.getApplicationID();
|
||||||
|
app.context.getApplications().remove(appId);
|
||||||
|
app.aclsManager.removeApplication(appId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,6 +397,6 @@ public class ApplicationImpl implements Application {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return ConverterUtils.toString(appId);
|
return appId.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,11 +119,11 @@ public class ContainerLaunch implements Callable<Integer> {
|
||||||
// /////////////////////////// Variable expansion
|
// /////////////////////////// Variable expansion
|
||||||
// Before the container script gets written out.
|
// Before the container script gets written out.
|
||||||
List<String> newCmds = new ArrayList<String>(command.size());
|
List<String> newCmds = new ArrayList<String>(command.size());
|
||||||
String appIdStr = app.toString();
|
String appIdStr = app.getAppId().toString();
|
||||||
Path containerLogDir =
|
Path containerLogDir =
|
||||||
this.logDirsSelector.getLocalPathForWrite(appIdStr + Path.SEPARATOR
|
this.logDirsSelector.getLocalPathForWrite(ContainerLaunch
|
||||||
+ containerIdStr, LocalDirAllocator.SIZE_UNKNOWN, this.conf,
|
.getRelativeContainerLogDir(appIdStr, containerIdStr),
|
||||||
false);
|
LocalDirAllocator.SIZE_UNKNOWN, this.conf, false);
|
||||||
for (String str : command) {
|
for (String str : command) {
|
||||||
// TODO: Should we instead work via symlinks without this grammar?
|
// TODO: Should we instead work via symlinks without this grammar?
|
||||||
newCmds.add(str.replace(ApplicationConstants.LOG_DIR_EXPANSION_VAR,
|
newCmds.add(str.replace(ApplicationConstants.LOG_DIR_EXPANSION_VAR,
|
||||||
|
@ -384,6 +384,11 @@ public class ContainerLaunch implements Callable<Integer> {
|
||||||
return processId;
|
return processId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getRelativeContainerLogDir(String appIdStr,
|
||||||
|
String containerIdStr) {
|
||||||
|
return appIdStr + Path.SEPARATOR + containerIdStr;
|
||||||
|
}
|
||||||
|
|
||||||
private String getContainerPrivateDir(String appIdStr, String containerIdStr) {
|
private String getContainerPrivateDir(String appIdStr, String containerIdStr) {
|
||||||
return getAppPrivateDir(appIdStr) + Path.SEPARATOR + containerIdStr
|
return getAppPrivateDir(appIdStr) + Path.SEPARATOR + containerIdStr
|
||||||
+ Path.SEPARATOR;
|
+ Path.SEPARATOR;
|
||||||
|
|
|
@ -25,10 +25,16 @@ import java.io.DataOutputStream;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.Writer;
|
||||||
import java.security.PrivilegedExceptionAction;
|
import java.security.PrivilegedExceptionAction;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import org.apache.commons.io.input.BoundedInputStream;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
@ -41,6 +47,8 @@ import org.apache.hadoop.fs.Path;
|
||||||
import org.apache.hadoop.io.Writable;
|
import org.apache.hadoop.io.Writable;
|
||||||
import org.apache.hadoop.io.file.tfile.TFile;
|
import org.apache.hadoop.io.file.tfile.TFile;
|
||||||
import org.apache.hadoop.security.UserGroupInformation;
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
|
import org.apache.hadoop.yarn.YarnException;
|
||||||
|
import org.apache.hadoop.yarn.api.records.ApplicationAccessType;
|
||||||
import org.apache.hadoop.yarn.api.records.ContainerId;
|
import org.apache.hadoop.yarn.api.records.ContainerId;
|
||||||
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
||||||
import org.apache.hadoop.yarn.util.ConverterUtils;
|
import org.apache.hadoop.yarn.util.ConverterUtils;
|
||||||
|
@ -48,32 +56,50 @@ import org.apache.hadoop.yarn.util.ConverterUtils;
|
||||||
public class AggregatedLogFormat {
|
public class AggregatedLogFormat {
|
||||||
|
|
||||||
static final Log LOG = LogFactory.getLog(AggregatedLogFormat.class);
|
static final Log LOG = LogFactory.getLog(AggregatedLogFormat.class);
|
||||||
|
private static final LogKey APPLICATION_ACL_KEY = new LogKey("APPLICATION_ACL");
|
||||||
|
private static final LogKey APPLICATION_OWNER_KEY = new LogKey("APPLICATION_OWNER");
|
||||||
|
private static final LogKey VERSION_KEY = new LogKey("VERSION");
|
||||||
|
private static final Map<String, LogKey> RESERVED_KEYS;
|
||||||
|
//Maybe write out the retention policy.
|
||||||
|
//Maybe write out a list of containerLogs skipped by the retention policy.
|
||||||
|
private static final int VERSION = 1;
|
||||||
|
|
||||||
|
static {
|
||||||
|
RESERVED_KEYS = new HashMap<String, AggregatedLogFormat.LogKey>();
|
||||||
|
RESERVED_KEYS.put(APPLICATION_ACL_KEY.toString(), APPLICATION_ACL_KEY);
|
||||||
|
RESERVED_KEYS.put(APPLICATION_OWNER_KEY.toString(), APPLICATION_OWNER_KEY);
|
||||||
|
RESERVED_KEYS.put(VERSION_KEY.toString(), VERSION_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
public static class LogKey implements Writable {
|
public static class LogKey implements Writable {
|
||||||
|
|
||||||
private String containerId;
|
private String keyString;
|
||||||
|
|
||||||
public LogKey() {
|
public LogKey() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LogKey(ContainerId containerId) {
|
public LogKey(ContainerId containerId) {
|
||||||
this.containerId = ConverterUtils.toString(containerId);
|
this.keyString = containerId.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LogKey(String keyString) {
|
||||||
|
this.keyString = keyString;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(DataOutput out) throws IOException {
|
public void write(DataOutput out) throws IOException {
|
||||||
out.writeUTF(this.containerId);
|
out.writeUTF(this.keyString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readFields(DataInput in) throws IOException {
|
public void readFields(DataInput in) throws IOException {
|
||||||
this.containerId = in.readUTF();
|
this.keyString = in.readUTF();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return this.containerId;
|
return this.keyString;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +107,8 @@ public class AggregatedLogFormat {
|
||||||
|
|
||||||
private final String[] rootLogDirs;
|
private final String[] rootLogDirs;
|
||||||
private final ContainerId containerId;
|
private final ContainerId containerId;
|
||||||
|
// TODO Maybe add a version string here. Instead of changing the version of
|
||||||
|
// the entire k-v format
|
||||||
|
|
||||||
public LogValue(String[] rootLogDirs, ContainerId containerId) {
|
public LogValue(String[] rootLogDirs, ContainerId containerId) {
|
||||||
this.rootLogDirs = rootLogDirs;
|
this.rootLogDirs = rootLogDirs;
|
||||||
|
@ -141,7 +169,8 @@ public class AggregatedLogFormat {
|
||||||
public FSDataOutputStream run() throws Exception {
|
public FSDataOutputStream run() throws Exception {
|
||||||
return FileContext.getFileContext(conf).create(
|
return FileContext.getFileContext(conf).create(
|
||||||
remoteAppLogFile,
|
remoteAppLogFile,
|
||||||
EnumSet.of(CreateFlag.CREATE), new Options.CreateOpts[] {});
|
EnumSet.of(CreateFlag.CREATE, CreateFlag.OVERWRITE),
|
||||||
|
new Options.CreateOpts[] {});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
|
@ -154,6 +183,40 @@ public class AggregatedLogFormat {
|
||||||
new TFile.Writer(this.fsDataOStream, 256 * 1024, conf.get(
|
new TFile.Writer(this.fsDataOStream, 256 * 1024, conf.get(
|
||||||
YarnConfiguration.NM_LOG_AGG_COMPRESSION_TYPE,
|
YarnConfiguration.NM_LOG_AGG_COMPRESSION_TYPE,
|
||||||
YarnConfiguration.DEFAULT_NM_LOG_AGG_COMPRESSION_TYPE), null, conf);
|
YarnConfiguration.DEFAULT_NM_LOG_AGG_COMPRESSION_TYPE), null, conf);
|
||||||
|
//Write the version string
|
||||||
|
writeVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeVersion() throws IOException {
|
||||||
|
DataOutputStream out = this.writer.prepareAppendKey(-1);
|
||||||
|
VERSION_KEY.write(out);
|
||||||
|
out.close();
|
||||||
|
out = this.writer.prepareAppendValue(-1);
|
||||||
|
out.writeInt(VERSION);
|
||||||
|
out.close();
|
||||||
|
this.fsDataOStream.hflush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeApplicationOwner(String user) throws IOException {
|
||||||
|
DataOutputStream out = this.writer.prepareAppendKey(-1);
|
||||||
|
APPLICATION_OWNER_KEY.write(out);
|
||||||
|
out.close();
|
||||||
|
out = this.writer.prepareAppendValue(-1);
|
||||||
|
out.writeUTF(user);
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeApplicationACLs(Map<ApplicationAccessType, String> appAcls)
|
||||||
|
throws IOException {
|
||||||
|
DataOutputStream out = this.writer.prepareAppendKey(-1);
|
||||||
|
APPLICATION_ACL_KEY.write(out);
|
||||||
|
out.close();
|
||||||
|
out = this.writer.prepareAppendValue(-1);
|
||||||
|
for (Entry<ApplicationAccessType, String> entry : appAcls.entrySet()) {
|
||||||
|
out.writeUTF(entry.getKey().toString());
|
||||||
|
out.writeUTF(entry.getValue());
|
||||||
|
}
|
||||||
|
out.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void append(LogKey logKey, LogValue logValue) throws IOException {
|
public void append(LogKey logKey, LogValue logValue) throws IOException {
|
||||||
|
@ -184,12 +247,13 @@ public class AggregatedLogFormat {
|
||||||
|
|
||||||
private final FSDataInputStream fsDataIStream;
|
private final FSDataInputStream fsDataIStream;
|
||||||
private final TFile.Reader.Scanner scanner;
|
private final TFile.Reader.Scanner scanner;
|
||||||
|
private final TFile.Reader reader;
|
||||||
|
|
||||||
public LogReader(Configuration conf, Path remoteAppLogFile)
|
public LogReader(Configuration conf, Path remoteAppLogFile)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
FileContext fileContext = FileContext.getFileContext(conf);
|
FileContext fileContext = FileContext.getFileContext(conf);
|
||||||
this.fsDataIStream = fileContext.open(remoteAppLogFile);
|
this.fsDataIStream = fileContext.open(remoteAppLogFile);
|
||||||
TFile.Reader reader =
|
reader =
|
||||||
new TFile.Reader(this.fsDataIStream, fileContext.getFileStatus(
|
new TFile.Reader(this.fsDataIStream, fileContext.getFileStatus(
|
||||||
remoteAppLogFile).getLen(), conf);
|
remoteAppLogFile).getLen(), conf);
|
||||||
this.scanner = reader.createScanner();
|
this.scanner = reader.createScanner();
|
||||||
|
@ -197,6 +261,69 @@ public class AggregatedLogFormat {
|
||||||
|
|
||||||
private boolean atBeginning = true;
|
private boolean atBeginning = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the owner of the application.
|
||||||
|
*
|
||||||
|
* @return the application owner.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public String getApplicationOwner() throws IOException {
|
||||||
|
TFile.Reader.Scanner ownerScanner = reader.createScanner();
|
||||||
|
LogKey key = new LogKey();
|
||||||
|
while (!ownerScanner.atEnd()) {
|
||||||
|
TFile.Reader.Scanner.Entry entry = ownerScanner.entry();
|
||||||
|
key.readFields(entry.getKeyStream());
|
||||||
|
if (key.toString().equals(APPLICATION_OWNER_KEY.toString())) {
|
||||||
|
DataInputStream valueStream = entry.getValueStream();
|
||||||
|
return valueStream.readUTF();
|
||||||
|
}
|
||||||
|
ownerScanner.advance();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns ACLs for the application. An empty map is returned if no ACLs are
|
||||||
|
* found.
|
||||||
|
*
|
||||||
|
* @return a map of the Application ACLs.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public Map<ApplicationAccessType, String> getApplicationAcls()
|
||||||
|
throws IOException {
|
||||||
|
// TODO Seek directly to the key once a comparator is specified.
|
||||||
|
TFile.Reader.Scanner aclScanner = reader.createScanner();
|
||||||
|
LogKey key = new LogKey();
|
||||||
|
Map<ApplicationAccessType, String> acls =
|
||||||
|
new HashMap<ApplicationAccessType, String>();
|
||||||
|
while (!aclScanner.atEnd()) {
|
||||||
|
TFile.Reader.Scanner.Entry entry = aclScanner.entry();
|
||||||
|
key.readFields(entry.getKeyStream());
|
||||||
|
if (key.toString().equals(APPLICATION_ACL_KEY.toString())) {
|
||||||
|
DataInputStream valueStream = entry.getValueStream();
|
||||||
|
while (true) {
|
||||||
|
String appAccessOp = null;
|
||||||
|
String aclString = null;
|
||||||
|
try {
|
||||||
|
appAccessOp = valueStream.readUTF();
|
||||||
|
} catch (EOFException e) {
|
||||||
|
// Valid end of stream.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
aclString = valueStream.readUTF();
|
||||||
|
} catch (EOFException e) {
|
||||||
|
throw new YarnException("Error reading ACLs", e);
|
||||||
|
}
|
||||||
|
acls.put(ApplicationAccessType.valueOf(appAccessOp), aclString);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
aclScanner.advance();
|
||||||
|
}
|
||||||
|
return acls;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read the next key and return the value-stream.
|
* Read the next key and return the value-stream.
|
||||||
*
|
*
|
||||||
|
@ -215,10 +342,99 @@ public class AggregatedLogFormat {
|
||||||
}
|
}
|
||||||
TFile.Reader.Scanner.Entry entry = this.scanner.entry();
|
TFile.Reader.Scanner.Entry entry = this.scanner.entry();
|
||||||
key.readFields(entry.getKeyStream());
|
key.readFields(entry.getKeyStream());
|
||||||
|
// Skip META keys
|
||||||
|
if (RESERVED_KEYS.containsKey(key.toString())) {
|
||||||
|
return next(key);
|
||||||
|
}
|
||||||
DataInputStream valueStream = entry.getValueStream();
|
DataInputStream valueStream = entry.getValueStream();
|
||||||
return valueStream;
|
return valueStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//TODO Change Log format and interfaces to be containerId specific.
|
||||||
|
// Avoid returning completeValueStreams.
|
||||||
|
// public List<String> getTypesForContainer(DataInputStream valueStream){}
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * @param valueStream
|
||||||
|
// * The Log stream for the container.
|
||||||
|
// * @param fileType
|
||||||
|
// * the log type required.
|
||||||
|
// * @return An InputStreamReader for the required log type or null if the
|
||||||
|
// * type is not found.
|
||||||
|
// * @throws IOException
|
||||||
|
// */
|
||||||
|
// public InputStreamReader getLogStreamForType(DataInputStream valueStream,
|
||||||
|
// String fileType) throws IOException {
|
||||||
|
// valueStream.reset();
|
||||||
|
// try {
|
||||||
|
// while (true) {
|
||||||
|
// String ft = valueStream.readUTF();
|
||||||
|
// String fileLengthStr = valueStream.readUTF();
|
||||||
|
// long fileLength = Long.parseLong(fileLengthStr);
|
||||||
|
// if (ft.equals(fileType)) {
|
||||||
|
// BoundedInputStream bis =
|
||||||
|
// new BoundedInputStream(valueStream, fileLength);
|
||||||
|
// return new InputStreamReader(bis);
|
||||||
|
// } else {
|
||||||
|
// long totalSkipped = 0;
|
||||||
|
// long currSkipped = 0;
|
||||||
|
// while (currSkipped != -1 && totalSkipped < fileLength) {
|
||||||
|
// currSkipped = valueStream.skip(fileLength - totalSkipped);
|
||||||
|
// totalSkipped += currSkipped;
|
||||||
|
// }
|
||||||
|
// // TODO Verify skip behaviour.
|
||||||
|
// if (currSkipped == -1) {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } catch (EOFException e) {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes all logs for a single container to the provided writer.
|
||||||
|
* @param valueStream
|
||||||
|
* @param writer
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static void readAcontainerLogs(DataInputStream valueStream,
|
||||||
|
Writer writer) throws IOException {
|
||||||
|
int bufferSize = 65536;
|
||||||
|
char[] cbuf = new char[bufferSize];
|
||||||
|
String fileType;
|
||||||
|
String fileLengthStr;
|
||||||
|
long fileLength;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
fileType = valueStream.readUTF();
|
||||||
|
} catch (EOFException e) {
|
||||||
|
// EndOfFile
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fileLengthStr = valueStream.readUTF();
|
||||||
|
fileLength = Long.parseLong(fileLengthStr);
|
||||||
|
writer.write("\n\nLogType:");
|
||||||
|
writer.write(fileType);
|
||||||
|
writer.write("\nLogLength:");
|
||||||
|
writer.write(fileLengthStr);
|
||||||
|
writer.write("\nLog Contents:\n");
|
||||||
|
// ByteLevel
|
||||||
|
BoundedInputStream bis =
|
||||||
|
new BoundedInputStream(valueStream, fileLength);
|
||||||
|
InputStreamReader reader = new InputStreamReader(bis);
|
||||||
|
int currentRead = 0;
|
||||||
|
int totalRead = 0;
|
||||||
|
while ((currentRead = reader.read(cbuf, 0, bufferSize)) != -1) {
|
||||||
|
writer.write(cbuf);
|
||||||
|
totalRead += currentRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keep calling this till you get a {@link EOFException} for getting logs of
|
* Keep calling this till you get a {@link EOFException} for getting logs of
|
||||||
* all types for a single container.
|
* all types for a single container.
|
||||||
|
|
|
@ -18,8 +18,9 @@
|
||||||
|
|
||||||
package org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation;
|
package org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.PrivilegedExceptionAction;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
@ -27,11 +28,16 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.fs.FileSystem;
|
||||||
import org.apache.hadoop.fs.Path;
|
import org.apache.hadoop.fs.Path;
|
||||||
import org.apache.hadoop.security.UserGroupInformation;
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
||||||
|
import org.apache.hadoop.yarn.api.records.ApplicationAccessType;
|
||||||
import org.apache.hadoop.yarn.api.records.ContainerId;
|
import org.apache.hadoop.yarn.api.records.ContainerId;
|
||||||
|
import org.apache.hadoop.yarn.event.Dispatcher;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.DeletionService;
|
import org.apache.hadoop.yarn.server.nodemanager.DeletionService;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.application.ApplicationEvent;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.application.ApplicationEventType;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.AggregatedLogFormat.LogKey;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.AggregatedLogFormat.LogKey;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.AggregatedLogFormat.LogValue;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.AggregatedLogFormat.LogValue;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.AggregatedLogFormat.LogWriter;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.AggregatedLogFormat.LogWriter;
|
||||||
|
@ -42,7 +48,10 @@ public class AppLogAggregatorImpl implements AppLogAggregator {
|
||||||
private static final Log LOG = LogFactory
|
private static final Log LOG = LogFactory
|
||||||
.getLog(AppLogAggregatorImpl.class);
|
.getLog(AppLogAggregatorImpl.class);
|
||||||
private static final int THREAD_SLEEP_TIME = 1000;
|
private static final int THREAD_SLEEP_TIME = 1000;
|
||||||
|
private static final String TMP_FILE_SUFFIX = ".tmp";
|
||||||
|
|
||||||
|
private final Dispatcher dispatcher;
|
||||||
|
private final ApplicationId appId;
|
||||||
private final String applicationId;
|
private final String applicationId;
|
||||||
private boolean logAggregationDisabled = false;
|
private boolean logAggregationDisabled = false;
|
||||||
private final Configuration conf;
|
private final Configuration conf;
|
||||||
|
@ -50,26 +59,34 @@ public class AppLogAggregatorImpl implements AppLogAggregator {
|
||||||
private final UserGroupInformation userUgi;
|
private final UserGroupInformation userUgi;
|
||||||
private final String[] rootLogDirs;
|
private final String[] rootLogDirs;
|
||||||
private final Path remoteNodeLogFileForApp;
|
private final Path remoteNodeLogFileForApp;
|
||||||
|
private final Path remoteNodeTmpLogFileForApp;
|
||||||
private final ContainerLogsRetentionPolicy retentionPolicy;
|
private final ContainerLogsRetentionPolicy retentionPolicy;
|
||||||
|
|
||||||
private final BlockingQueue<ContainerId> pendingContainers;
|
private final BlockingQueue<ContainerId> pendingContainers;
|
||||||
private final AtomicBoolean appFinishing = new AtomicBoolean();
|
private final AtomicBoolean appFinishing = new AtomicBoolean();
|
||||||
private final AtomicBoolean appAggregationFinished = new AtomicBoolean();
|
private final AtomicBoolean appAggregationFinished = new AtomicBoolean();
|
||||||
|
private final Map<ApplicationAccessType, String> appAcls;
|
||||||
|
|
||||||
private LogWriter writer = null;
|
private LogWriter writer = null;
|
||||||
|
|
||||||
public AppLogAggregatorImpl(DeletionService deletionService,
|
public AppLogAggregatorImpl(Dispatcher dispatcher,
|
||||||
Configuration conf, ApplicationId appId, UserGroupInformation userUgi,
|
DeletionService deletionService, Configuration conf, ApplicationId appId,
|
||||||
String[] localRootLogDirs, Path remoteNodeLogFileForApp,
|
UserGroupInformation userUgi, String[] localRootLogDirs,
|
||||||
ContainerLogsRetentionPolicy retentionPolicy) {
|
Path remoteNodeLogFileForApp,
|
||||||
|
ContainerLogsRetentionPolicy retentionPolicy,
|
||||||
|
Map<ApplicationAccessType, String> appAcls) {
|
||||||
|
this.dispatcher = dispatcher;
|
||||||
this.conf = conf;
|
this.conf = conf;
|
||||||
this.delService = deletionService;
|
this.delService = deletionService;
|
||||||
|
this.appId = appId;
|
||||||
this.applicationId = ConverterUtils.toString(appId);
|
this.applicationId = ConverterUtils.toString(appId);
|
||||||
this.userUgi = userUgi;
|
this.userUgi = userUgi;
|
||||||
this.rootLogDirs = localRootLogDirs;
|
this.rootLogDirs = localRootLogDirs;
|
||||||
this.remoteNodeLogFileForApp = remoteNodeLogFileForApp;
|
this.remoteNodeLogFileForApp = remoteNodeLogFileForApp;
|
||||||
|
this.remoteNodeTmpLogFileForApp = getRemoteNodeTmpLogFileForApp();
|
||||||
this.retentionPolicy = retentionPolicy;
|
this.retentionPolicy = retentionPolicy;
|
||||||
this.pendingContainers = new LinkedBlockingQueue<ContainerId>();
|
this.pendingContainers = new LinkedBlockingQueue<ContainerId>();
|
||||||
|
this.appAcls = appAcls;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void uploadLogsForContainer(ContainerId containerId) {
|
private void uploadLogsForContainer(ContainerId containerId) {
|
||||||
|
@ -80,11 +97,15 @@ public class AppLogAggregatorImpl implements AppLogAggregator {
|
||||||
|
|
||||||
// Lazy creation of the writer
|
// Lazy creation of the writer
|
||||||
if (this.writer == null) {
|
if (this.writer == null) {
|
||||||
LOG.info("Starting aggregate log-file for app " + this.applicationId);
|
LOG.info("Starting aggregate log-file for app " + this.applicationId
|
||||||
|
+ " at " + this.remoteNodeTmpLogFileForApp);
|
||||||
try {
|
try {
|
||||||
this.writer =
|
this.writer =
|
||||||
new LogWriter(this.conf, this.remoteNodeLogFileForApp,
|
new LogWriter(this.conf, this.remoteNodeTmpLogFileForApp,
|
||||||
this.userUgi);
|
this.userUgi);
|
||||||
|
//Write ACLs once when and if the writer is created.
|
||||||
|
this.writer.writeApplicationACLs(appAcls);
|
||||||
|
this.writer.writeApplicationOwner(this.userUgi.getShortUserName());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.error("Cannot create writer for app " + this.applicationId
|
LOG.error("Cannot create writer for app " + this.applicationId
|
||||||
+ ". Disabling log-aggregation for this app.", e);
|
+ ". Disabling log-aggregation for this app.", e);
|
||||||
|
@ -105,8 +126,8 @@ public class AppLogAggregatorImpl implements AppLogAggregator {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
@SuppressWarnings("unchecked")
|
||||||
|
public void run() {
|
||||||
ContainerId containerId;
|
ContainerId containerId;
|
||||||
|
|
||||||
while (!this.appFinishing.get()) {
|
while (!this.appFinishing.get()) {
|
||||||
|
@ -141,10 +162,33 @@ public class AppLogAggregatorImpl implements AppLogAggregator {
|
||||||
this.writer.closeWriter();
|
this.writer.closeWriter();
|
||||||
LOG.info("Finished aggregate log-file for app " + this.applicationId);
|
LOG.info("Finished aggregate log-file for app " + this.applicationId);
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
userUgi.doAs(new PrivilegedExceptionAction<Object>() {
|
||||||
|
@Override
|
||||||
|
public Object run() throws Exception {
|
||||||
|
FileSystem remoteFS = FileSystem.get(conf);
|
||||||
|
remoteFS.rename(remoteNodeTmpLogFileForApp, remoteNodeLogFileForApp);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Failed to move temporary log file to final location: ["
|
||||||
|
+ remoteNodeTmpLogFileForApp + "] to [" + remoteNodeLogFileForApp
|
||||||
|
+ "]", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dispatcher.getEventHandler().handle(
|
||||||
|
new ApplicationEvent(this.appId,
|
||||||
|
ApplicationEventType.APPLICATION_LOG_AGGREGATION_FINISHED));
|
||||||
|
|
||||||
this.appAggregationFinished.set(true);
|
this.appAggregationFinished.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Path getRemoteNodeTmpLogFileForApp() {
|
||||||
|
return new Path(remoteNodeLogFileForApp.getParent(),
|
||||||
|
(remoteNodeLogFileForApp.getName() + TMP_FILE_SUFFIX));
|
||||||
|
}
|
||||||
|
|
||||||
private boolean shouldUploadLogs(ContainerId containerId,
|
private boolean shouldUploadLogs(ContainerId containerId,
|
||||||
boolean wasContainerSuccessful) {
|
boolean wasContainerSuccessful) {
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,9 @@
|
||||||
|
|
||||||
package org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation;
|
package org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.io.IOException;
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.security.PrivilegedExceptionAction;
|
import java.security.PrivilegedExceptionAction;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
@ -31,20 +31,26 @@ import org.apache.commons.logging.LogFactory;
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
import org.apache.hadoop.fs.FileSystem;
|
import org.apache.hadoop.fs.FileSystem;
|
||||||
import org.apache.hadoop.fs.Path;
|
import org.apache.hadoop.fs.Path;
|
||||||
|
import org.apache.hadoop.fs.permission.FsPermission;
|
||||||
import org.apache.hadoop.security.Credentials;
|
import org.apache.hadoop.security.Credentials;
|
||||||
import org.apache.hadoop.security.UserGroupInformation;
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
import org.apache.hadoop.security.token.Token;
|
import org.apache.hadoop.security.token.Token;
|
||||||
import org.apache.hadoop.security.token.TokenIdentifier;
|
import org.apache.hadoop.security.token.TokenIdentifier;
|
||||||
import org.apache.hadoop.yarn.YarnException;
|
import org.apache.hadoop.yarn.YarnException;
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
||||||
|
import org.apache.hadoop.yarn.api.records.ApplicationAccessType;
|
||||||
import org.apache.hadoop.yarn.api.records.ContainerId;
|
import org.apache.hadoop.yarn.api.records.ContainerId;
|
||||||
|
import org.apache.hadoop.yarn.api.records.NodeId;
|
||||||
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
||||||
|
import org.apache.hadoop.yarn.event.Dispatcher;
|
||||||
import org.apache.hadoop.yarn.event.EventHandler;
|
import org.apache.hadoop.yarn.event.EventHandler;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.Context;
|
import org.apache.hadoop.yarn.server.nodemanager.Context;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.DeletionService;
|
import org.apache.hadoop.yarn.server.nodemanager.DeletionService;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.event.LogAggregatorAppFinishedEvent;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.event.LogAggregatorAppStartedEvent;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.event.LogAggregatorContainerFinishedEvent;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.event.LogAggregatorEvent;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.event.LogAggregatorEvent;
|
||||||
import org.apache.hadoop.yarn.service.AbstractService;
|
import org.apache.hadoop.yarn.service.AbstractService;
|
||||||
import org.apache.hadoop.yarn.util.ConverterUtils;
|
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
|
||||||
|
@ -54,20 +60,43 @@ public class LogAggregationService extends AbstractService implements
|
||||||
private static final Log LOG = LogFactory
|
private static final Log LOG = LogFactory
|
||||||
.getLog(LogAggregationService.class);
|
.getLog(LogAggregationService.class);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Expected deployment TLD will be 1777, owner=<NMOwner>, group=<NMGroup -
|
||||||
|
* Group to which NMOwner belongs> App dirs will be created as 750,
|
||||||
|
* owner=<AppOwner>, group=<NMGroup>: so that the owner and <NMOwner> can
|
||||||
|
* access / modify the files.
|
||||||
|
* <NMGroup> should obviously be a limited access group.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Permissions for the top level directory under which app directories will be
|
||||||
|
* created.
|
||||||
|
*/
|
||||||
|
private static final FsPermission TLDIR_PERMISSIONS = FsPermission
|
||||||
|
.createImmutable((short) 01777);
|
||||||
|
/**
|
||||||
|
* Permissions for the Application directory.
|
||||||
|
*/
|
||||||
|
private static final FsPermission APP_DIR_PERMISSIONS = FsPermission
|
||||||
|
.createImmutable((short) 0750);
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final DeletionService deletionService;
|
private final DeletionService deletionService;
|
||||||
|
private final Dispatcher dispatcher;
|
||||||
|
|
||||||
private String[] localRootLogDirs;
|
private String[] localRootLogDirs;
|
||||||
Path remoteRootLogDir;
|
Path remoteRootLogDir;
|
||||||
private String nodeFile;
|
String remoteRootLogDirSuffix;
|
||||||
|
private NodeId nodeId;
|
||||||
|
private boolean isLogAggregationEnabled = false;
|
||||||
|
|
||||||
private final ConcurrentMap<ApplicationId, AppLogAggregator> appLogAggregators;
|
private final ConcurrentMap<ApplicationId, AppLogAggregator> appLogAggregators;
|
||||||
|
|
||||||
private final ExecutorService threadPool;
|
private final ExecutorService threadPool;
|
||||||
|
|
||||||
public LogAggregationService(Context context,
|
public LogAggregationService(Dispatcher dispatcher, Context context,
|
||||||
DeletionService deletionService) {
|
DeletionService deletionService) {
|
||||||
super(LogAggregationService.class.getName());
|
super(LogAggregationService.class.getName());
|
||||||
|
this.dispatcher = dispatcher;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.deletionService = deletionService;
|
this.deletionService = deletionService;
|
||||||
this.appLogAggregators =
|
this.appLogAggregators =
|
||||||
|
@ -80,10 +109,17 @@ public class LogAggregationService extends AbstractService implements
|
||||||
|
|
||||||
public synchronized void init(Configuration conf) {
|
public synchronized void init(Configuration conf) {
|
||||||
this.localRootLogDirs =
|
this.localRootLogDirs =
|
||||||
conf.getStrings(YarnConfiguration.NM_LOG_DIRS, YarnConfiguration.DEFAULT_NM_LOG_DIRS);
|
conf.getStrings(YarnConfiguration.NM_LOG_DIRS,
|
||||||
|
YarnConfiguration.DEFAULT_NM_LOG_DIRS);
|
||||||
this.remoteRootLogDir =
|
this.remoteRootLogDir =
|
||||||
new Path(conf.get(YarnConfiguration.NM_REMOTE_APP_LOG_DIR,
|
new Path(conf.get(YarnConfiguration.NM_REMOTE_APP_LOG_DIR,
|
||||||
YarnConfiguration.DEFAULT_NM_REMOTE_APP_LOG_DIR));
|
YarnConfiguration.DEFAULT_NM_REMOTE_APP_LOG_DIR));
|
||||||
|
this.remoteRootLogDirSuffix =
|
||||||
|
conf.get(YarnConfiguration.NM_REMOTE_APP_LOG_DIR_SUFFIX,
|
||||||
|
YarnConfiguration.DEFAULT_NM_REMOTE_APP_LOG_DIR_SUFFIX);
|
||||||
|
this.isLogAggregationEnabled =
|
||||||
|
conf.getBoolean(YarnConfiguration.NM_LOG_AGGREGATION_ENABLED, false);
|
||||||
|
|
||||||
super.init(conf);
|
super.init(conf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,26 +127,11 @@ public class LogAggregationService extends AbstractService implements
|
||||||
public synchronized void start() {
|
public synchronized void start() {
|
||||||
// NodeId is only available during start, the following cannot be moved
|
// NodeId is only available during start, the following cannot be moved
|
||||||
// anywhere else.
|
// anywhere else.
|
||||||
this.nodeFile = this.context.getNodeId().toString();
|
this.nodeId = this.context.getNodeId();
|
||||||
|
verifyAndCreateRemoteLogDir(getConfig());
|
||||||
super.start();
|
super.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
Path getRemoteNodeLogFileForApp(ApplicationId appId) {
|
|
||||||
return getRemoteNodeLogFileForApp(this.remoteRootLogDir, appId,
|
|
||||||
this.nodeFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Path getRemoteNodeLogFileForApp(Path remoteRootLogDir,
|
|
||||||
ApplicationId appId, String nodeFile) {
|
|
||||||
return new Path(getRemoteAppLogDir(remoteRootLogDir, appId),
|
|
||||||
nodeFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Path getRemoteAppLogDir(Path remoteRootLogDir,
|
|
||||||
ApplicationId appId) {
|
|
||||||
return new Path(remoteRootLogDir, ConverterUtils.toString(appId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void stop() {
|
public synchronized void stop() {
|
||||||
LOG.info(this.getName() + " waiting for pending aggregation during exit");
|
LOG.info(this.getName() + " waiting for pending aggregation during exit");
|
||||||
|
@ -120,8 +141,217 @@ public class LogAggregationService extends AbstractService implements
|
||||||
super.stop();
|
super.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the full filename for an application's log file per node.
|
||||||
|
* @param remoteRootLogDir
|
||||||
|
* @param appId
|
||||||
|
* @param user
|
||||||
|
* @param nodeId
|
||||||
|
* @param suffix
|
||||||
|
* @return the remote log file.
|
||||||
|
*/
|
||||||
|
public static Path getRemoteNodeLogFileForApp(Path remoteRootLogDir,
|
||||||
|
ApplicationId appId, String user, NodeId nodeId, String suffix) {
|
||||||
|
return new Path(getRemoteAppLogDir(remoteRootLogDir, appId, user, suffix),
|
||||||
|
getNodeString(nodeId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the remote app log dir.
|
||||||
|
* @param remoteRootLogDir
|
||||||
|
* @param appId
|
||||||
|
* @param user
|
||||||
|
* @param suffix
|
||||||
|
* @return the remote application specific log dir.
|
||||||
|
*/
|
||||||
|
public static Path getRemoteAppLogDir(Path remoteRootLogDir,
|
||||||
|
ApplicationId appId, String user, String suffix) {
|
||||||
|
return new Path(getRemoteLogSuffixedDir(remoteRootLogDir, user, suffix),
|
||||||
|
appId.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the remote suffixed log dir for the user.
|
||||||
|
* @param remoteRootLogDir
|
||||||
|
* @param user
|
||||||
|
* @param suffix
|
||||||
|
* @return the remote suffixed log dir.
|
||||||
|
*/
|
||||||
|
private static Path getRemoteLogSuffixedDir(Path remoteRootLogDir,
|
||||||
|
String user, String suffix) {
|
||||||
|
if (suffix == null || suffix.isEmpty()) {
|
||||||
|
return getRemoteLogUserDir(remoteRootLogDir, user);
|
||||||
|
}
|
||||||
|
// TODO Maybe support suffix to be more than a single file.
|
||||||
|
return new Path(getRemoteLogUserDir(remoteRootLogDir, user), suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Add a utility method to list available log files. Ignore the
|
||||||
|
// temporary ones.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the remote log user dir.
|
||||||
|
* @param remoteRootLogDir
|
||||||
|
* @param user
|
||||||
|
* @return the remote per user log dir.
|
||||||
|
*/
|
||||||
|
private static Path getRemoteLogUserDir(Path remoteRootLogDir, String user) {
|
||||||
|
return new Path(remoteRootLogDir, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the suffix component of the log dir.
|
||||||
|
* @param conf
|
||||||
|
* @return the suffix which will be appended to the user log dir.
|
||||||
|
*/
|
||||||
|
public static String getRemoteNodeLogDirSuffix(Configuration conf) {
|
||||||
|
return conf.get(YarnConfiguration.NM_REMOTE_APP_LOG_DIR_SUFFIX,
|
||||||
|
YarnConfiguration.DEFAULT_NM_REMOTE_APP_LOG_DIR_SUFFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a nodeId to a form used in the app log file name.
|
||||||
|
* @param nodeId
|
||||||
|
* @return the node string to be used to construct the file name.
|
||||||
|
*/
|
||||||
|
private static String getNodeString(NodeId nodeId) {
|
||||||
|
return nodeId.toString().replace(":", "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void verifyAndCreateRemoteLogDir(Configuration conf) {
|
||||||
|
// Checking the existance of the TLD
|
||||||
|
FileSystem remoteFS = null;
|
||||||
|
try {
|
||||||
|
remoteFS = FileSystem.get(conf);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new YarnException("Unable to get Remote FileSystem isntance", e);
|
||||||
|
}
|
||||||
|
boolean remoteExists = false;
|
||||||
|
try {
|
||||||
|
remoteExists = remoteFS.exists(this.remoteRootLogDir);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new YarnException("Failed to check for existance of remoteLogDir ["
|
||||||
|
+ this.remoteRootLogDir + "]");
|
||||||
|
}
|
||||||
|
if (remoteExists) {
|
||||||
|
try {
|
||||||
|
FsPermission perms =
|
||||||
|
remoteFS.getFileStatus(this.remoteRootLogDir).getPermission();
|
||||||
|
if (!perms.equals(TLDIR_PERMISSIONS)) {
|
||||||
|
LOG.warn("Remote Root Log Dir [" + this.remoteRootLogDir
|
||||||
|
+ "] already exist, but with incorrect permissions. "
|
||||||
|
+ "Expected: [" + TLDIR_PERMISSIONS + "], Found: [" + perms
|
||||||
|
+ "]." + " The cluster may have problems with multiple users.");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new YarnException(
|
||||||
|
"Failed while attempting to check permissions for dir ["
|
||||||
|
+ this.remoteRootLogDir + "]");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.warn("Remote Root Log Dir [" + this.remoteRootLogDir
|
||||||
|
+ "] does not exist. Attempting to create it.");
|
||||||
|
try {
|
||||||
|
Path qualified =
|
||||||
|
this.remoteRootLogDir.makeQualified(remoteFS.getUri(),
|
||||||
|
remoteFS.getWorkingDirectory());
|
||||||
|
remoteFS.mkdirs(qualified, new FsPermission(TLDIR_PERMISSIONS));
|
||||||
|
remoteFS.setPermission(qualified, new FsPermission(TLDIR_PERMISSIONS));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new YarnException("Failed to create remoteLogDir ["
|
||||||
|
+ this.remoteRootLogDir + "]", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Path getRemoteNodeLogFileForApp(ApplicationId appId, String user) {
|
||||||
|
return LogAggregationService.getRemoteNodeLogFileForApp(
|
||||||
|
this.remoteRootLogDir, appId, user, this.nodeId,
|
||||||
|
this.remoteRootLogDirSuffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createDir(FileSystem fs, Path path, FsPermission fsPerm)
|
||||||
|
throws IOException {
|
||||||
|
fs.mkdirs(path, new FsPermission(fsPerm));
|
||||||
|
fs.setPermission(path, new FsPermission(fsPerm));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createAppDir(final String user, final ApplicationId appId,
|
||||||
|
UserGroupInformation userUgi) {
|
||||||
|
try {
|
||||||
|
userUgi.doAs(new PrivilegedExceptionAction<Object>() {
|
||||||
|
@Override
|
||||||
|
public Object run() throws Exception {
|
||||||
|
// TODO: Reuse FS for user?
|
||||||
|
FileSystem remoteFS = null;
|
||||||
|
Path userDir = null;
|
||||||
|
Path suffixDir = null;
|
||||||
|
Path appDir = null;
|
||||||
|
try {
|
||||||
|
remoteFS = FileSystem.get(getConfig());
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Failed to get remote FileSystem while processing app "
|
||||||
|
+ appId, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
userDir =
|
||||||
|
getRemoteLogUserDir(
|
||||||
|
LogAggregationService.this.remoteRootLogDir, user);
|
||||||
|
userDir =
|
||||||
|
userDir.makeQualified(remoteFS.getUri(),
|
||||||
|
remoteFS.getWorkingDirectory());
|
||||||
|
createDir(remoteFS, userDir, APP_DIR_PERMISSIONS);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Failed to create user dir [" + userDir
|
||||||
|
+ "] while processing app " + appId);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
suffixDir =
|
||||||
|
getRemoteLogSuffixedDir(
|
||||||
|
LogAggregationService.this.remoteRootLogDir, user,
|
||||||
|
LogAggregationService.this.remoteRootLogDirSuffix);
|
||||||
|
suffixDir =
|
||||||
|
suffixDir.makeQualified(remoteFS.getUri(),
|
||||||
|
remoteFS.getWorkingDirectory());
|
||||||
|
createDir(remoteFS, suffixDir, APP_DIR_PERMISSIONS);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Failed to create suffixed user dir [" + suffixDir
|
||||||
|
+ "] while processing app " + appId);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
appDir =
|
||||||
|
getRemoteAppLogDir(LogAggregationService.this.remoteRootLogDir,
|
||||||
|
appId, user,
|
||||||
|
LogAggregationService.this.remoteRootLogDirSuffix);
|
||||||
|
appDir =
|
||||||
|
appDir.makeQualified(remoteFS.getUri(),
|
||||||
|
remoteFS.getWorkingDirectory());
|
||||||
|
createDir(remoteFS, appDir, APP_DIR_PERMISSIONS);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Failed to create application log dir [" + appDir
|
||||||
|
+ "] while processing app " + appId);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new YarnException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void initApp(final ApplicationId appId, String user,
|
private void initApp(final ApplicationId appId, String user,
|
||||||
Credentials credentials, ContainerLogsRetentionPolicy logRetentionPolicy) {
|
Credentials credentials, ContainerLogsRetentionPolicy logRetentionPolicy,
|
||||||
|
Map<ApplicationAccessType, String> appAcls) {
|
||||||
|
|
||||||
// Get user's FileSystem credentials
|
// Get user's FileSystem credentials
|
||||||
UserGroupInformation userUgi =
|
UserGroupInformation userUgi =
|
||||||
|
@ -133,41 +363,27 @@ public class LogAggregationService extends AbstractService implements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create the app dir
|
||||||
|
createAppDir(user, appId, userUgi);
|
||||||
|
|
||||||
// New application
|
// New application
|
||||||
AppLogAggregator appLogAggregator =
|
AppLogAggregator appLogAggregator =
|
||||||
new AppLogAggregatorImpl(this.deletionService, getConfig(), appId,
|
new AppLogAggregatorImpl(this.dispatcher, this.deletionService, getConfig(), appId,
|
||||||
userUgi, this.localRootLogDirs,
|
userUgi, this.localRootLogDirs,
|
||||||
getRemoteNodeLogFileForApp(appId), logRetentionPolicy);
|
getRemoteNodeLogFileForApp(appId, user), logRetentionPolicy, appAcls);
|
||||||
if (this.appLogAggregators.putIfAbsent(appId, appLogAggregator) != null) {
|
if (this.appLogAggregators.putIfAbsent(appId, appLogAggregator) != null) {
|
||||||
throw new YarnException("Duplicate initApp for " + appId);
|
throw new YarnException("Duplicate initApp for " + appId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the app dir
|
|
||||||
try {
|
|
||||||
userUgi.doAs(new PrivilegedExceptionAction<Object>() {
|
|
||||||
@Override
|
|
||||||
public Object run() throws Exception {
|
|
||||||
// TODO: Reuse FS for user?
|
|
||||||
FileSystem remoteFS = FileSystem.get(getConfig());
|
|
||||||
remoteFS.mkdirs(getRemoteAppLogDir(
|
|
||||||
LogAggregationService.this.remoteRootLogDir, appId)
|
|
||||||
.makeQualified(remoteFS.getUri(),
|
|
||||||
remoteFS.getWorkingDirectory()));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new YarnException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the user configuration for the list of containers that need log
|
// TODO Get the user configuration for the list of containers that need log
|
||||||
// aggregation.
|
// aggregation.
|
||||||
|
|
||||||
// Schedule the aggregator.
|
// Schedule the aggregator.
|
||||||
this.threadPool.execute(appLogAggregator);
|
this.threadPool.execute(appLogAggregator);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopContainer(ContainerId containerId, String exitCode) {
|
private void stopContainer(ContainerId containerId, int exitCode) {
|
||||||
|
|
||||||
// A container is complete. Put this containers' logs up for aggregation if
|
// A container is complete. Put this containers' logs up for aggregation if
|
||||||
// this containers' logs are needed.
|
// this containers' logs are needed.
|
||||||
|
@ -179,7 +395,7 @@ public class LogAggregationService extends AbstractService implements
|
||||||
}
|
}
|
||||||
this.appLogAggregators.get(
|
this.appLogAggregators.get(
|
||||||
containerId.getApplicationAttemptId().getApplicationId())
|
containerId.getApplicationAttemptId().getApplicationId())
|
||||||
.startContainerLogAggregation(containerId, exitCode.equals("0"));
|
.startContainerLogAggregation(containerId, exitCode == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopApp(ApplicationId appId) {
|
private void stopApp(ApplicationId appId) {
|
||||||
|
@ -196,27 +412,30 @@ public class LogAggregationService extends AbstractService implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(LogAggregatorEvent event) {
|
public void handle(LogAggregatorEvent event) {
|
||||||
// switch (event.getType()) {
|
if (this.isLogAggregationEnabled) {
|
||||||
// case APPLICATION_STARTED:
|
switch (event.getType()) {
|
||||||
// LogAggregatorAppStartedEvent appStartEvent =
|
case APPLICATION_STARTED:
|
||||||
// (LogAggregatorAppStartedEvent) event;
|
LogAggregatorAppStartedEvent appStartEvent =
|
||||||
// initApp(appStartEvent.getApplicationId(), appStartEvent.getUser(),
|
(LogAggregatorAppStartedEvent) event;
|
||||||
// appStartEvent.getCredentials(),
|
initApp(appStartEvent.getApplicationId(), appStartEvent.getUser(),
|
||||||
// appStartEvent.getLogRetentionPolicy());
|
appStartEvent.getCredentials(),
|
||||||
// break;
|
appStartEvent.getLogRetentionPolicy(),
|
||||||
// case CONTAINER_FINISHED:
|
appStartEvent.getApplicationAcls());
|
||||||
// LogAggregatorContainerFinishedEvent containerFinishEvent =
|
break;
|
||||||
// (LogAggregatorContainerFinishedEvent) event;
|
case CONTAINER_FINISHED:
|
||||||
// stopContainer(containerFinishEvent.getContainerId(),
|
LogAggregatorContainerFinishedEvent containerFinishEvent =
|
||||||
// containerFinishEvent.getExitCode());
|
(LogAggregatorContainerFinishedEvent) event;
|
||||||
// break;
|
stopContainer(containerFinishEvent.getContainerId(),
|
||||||
// case APPLICATION_FINISHED:
|
containerFinishEvent.getExitCode());
|
||||||
// LogAggregatorAppFinishedEvent appFinishedEvent =
|
break;
|
||||||
// (LogAggregatorAppFinishedEvent) event;
|
case APPLICATION_FINISHED:
|
||||||
// stopApp(appFinishedEvent.getApplicationId());
|
LogAggregatorAppFinishedEvent appFinishedEvent =
|
||||||
// break;
|
(LogAggregatorAppFinishedEvent) event;
|
||||||
// default:
|
stopApp(appFinishedEvent.getApplicationId());
|
||||||
// ; // Ignore
|
break;
|
||||||
// }
|
default:
|
||||||
|
; // Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.apache.hadoop.fs.FileContext;
|
||||||
import org.apache.hadoop.fs.FileStatus;
|
import org.apache.hadoop.fs.FileStatus;
|
||||||
import org.apache.hadoop.fs.Path;
|
import org.apache.hadoop.fs.Path;
|
||||||
import org.apache.hadoop.fs.RemoteIterator;
|
import org.apache.hadoop.fs.RemoteIterator;
|
||||||
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
import org.apache.hadoop.util.Tool;
|
import org.apache.hadoop.util.Tool;
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
||||||
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
||||||
|
@ -110,7 +111,12 @@ public class LogDumper extends Configured implements Tool {
|
||||||
AggregatedLogFormat.LogReader reader =
|
AggregatedLogFormat.LogReader reader =
|
||||||
new AggregatedLogFormat.LogReader(getConf(),
|
new AggregatedLogFormat.LogReader(getConf(),
|
||||||
LogAggregationService.getRemoteNodeLogFileForApp(
|
LogAggregationService.getRemoteNodeLogFileForApp(
|
||||||
remoteRootLogDir, appId, nodeAddress));
|
remoteRootLogDir,
|
||||||
|
appId,
|
||||||
|
UserGroupInformation.getCurrentUser().getShortUserName(),
|
||||||
|
ConverterUtils.toNodeId(nodeAddress),
|
||||||
|
getConf().get(YarnConfiguration.NM_REMOTE_APP_LOG_DIR_SUFFIX,
|
||||||
|
YarnConfiguration.DEFAULT_NM_REMOTE_APP_LOG_DIR_SUFFIX)));
|
||||||
return dumpAContainerLogs(containerIdStr, reader, out);
|
return dumpAContainerLogs(containerIdStr, reader, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,16 +158,21 @@ public class LogDumper extends Configured implements Tool {
|
||||||
Path remoteRootLogDir =
|
Path remoteRootLogDir =
|
||||||
new Path(getConf().get(YarnConfiguration.NM_REMOTE_APP_LOG_DIR,
|
new Path(getConf().get(YarnConfiguration.NM_REMOTE_APP_LOG_DIR,
|
||||||
YarnConfiguration.DEFAULT_NM_REMOTE_APP_LOG_DIR));
|
YarnConfiguration.DEFAULT_NM_REMOTE_APP_LOG_DIR));
|
||||||
|
String user = UserGroupInformation.getCurrentUser().getShortUserName();
|
||||||
|
String logDirSuffix =
|
||||||
|
getConf().get(YarnConfiguration.NM_REMOTE_APP_LOG_DIR,
|
||||||
|
YarnConfiguration.DEFAULT_NM_REMOTE_APP_LOG_DIR_SUFFIX);
|
||||||
|
//TODO Change this to get a list of files from the LAS.
|
||||||
Path remoteAppLogDir =
|
Path remoteAppLogDir =
|
||||||
LogAggregationService.getRemoteAppLogDir(remoteRootLogDir, appId);
|
LogAggregationService.getRemoteAppLogDir(remoteRootLogDir, appId, user,
|
||||||
|
logDirSuffix);
|
||||||
RemoteIterator<FileStatus> nodeFiles =
|
RemoteIterator<FileStatus> nodeFiles =
|
||||||
FileContext.getFileContext().listStatus(remoteAppLogDir);
|
FileContext.getFileContext().listStatus(remoteAppLogDir);
|
||||||
while (nodeFiles.hasNext()) {
|
while (nodeFiles.hasNext()) {
|
||||||
FileStatus thisNodeFile = nodeFiles.next();
|
FileStatus thisNodeFile = nodeFiles.next();
|
||||||
AggregatedLogFormat.LogReader reader =
|
AggregatedLogFormat.LogReader reader =
|
||||||
new AggregatedLogFormat.LogReader(getConf(),
|
new AggregatedLogFormat.LogReader(getConf(),
|
||||||
LogAggregationService.getRemoteNodeLogFileForApp(
|
new Path(remoteAppLogDir, thisNodeFile.getPath().getName()));
|
||||||
remoteRootLogDir, appId, thisNodeFile.getPath().getName()));
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
DataInputStream valueStream;
|
DataInputStream valueStream;
|
||||||
|
|
|
@ -1,25 +1,28 @@
|
||||||
/**
|
/**
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
* or more contributor license agreements. See the NOTICE file
|
* or more contributor license agreements. See the NOTICE file
|
||||||
* distributed with this work for additional information
|
* distributed with this work for additional information
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
* to you under the Apache License, Version 2.0 (the
|
* to you under the Apache License, Version 2.0 (the
|
||||||
* "License"); you may not use this file except in compliance
|
* "License"); you may not use this file except in compliance
|
||||||
* with the License. You may obtain a copy of the License at
|
* with the License. You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.event;
|
package org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.event;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.hadoop.security.Credentials;
|
import org.apache.hadoop.security.Credentials;
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
||||||
|
import org.apache.hadoop.yarn.api.records.ApplicationAccessType;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.ContainerLogsRetentionPolicy;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.ContainerLogsRetentionPolicy;
|
||||||
|
|
||||||
public class LogAggregatorAppStartedEvent extends LogAggregatorEvent {
|
public class LogAggregatorAppStartedEvent extends LogAggregatorEvent {
|
||||||
|
@ -28,14 +31,17 @@ public class LogAggregatorAppStartedEvent extends LogAggregatorEvent {
|
||||||
private final ContainerLogsRetentionPolicy retentionPolicy;
|
private final ContainerLogsRetentionPolicy retentionPolicy;
|
||||||
private final String user;
|
private final String user;
|
||||||
private final Credentials credentials;
|
private final Credentials credentials;
|
||||||
|
private final Map<ApplicationAccessType, String> appAcls;
|
||||||
|
|
||||||
public LogAggregatorAppStartedEvent(ApplicationId appId, String user,
|
public LogAggregatorAppStartedEvent(ApplicationId appId, String user,
|
||||||
Credentials credentials, ContainerLogsRetentionPolicy retentionPolicy) {
|
Credentials credentials, ContainerLogsRetentionPolicy retentionPolicy,
|
||||||
|
Map<ApplicationAccessType, String> appAcls) {
|
||||||
super(LogAggregatorEventType.APPLICATION_STARTED);
|
super(LogAggregatorEventType.APPLICATION_STARTED);
|
||||||
this.applicationId = appId;
|
this.applicationId = appId;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.credentials = credentials;
|
this.credentials = credentials;
|
||||||
this.retentionPolicy = retentionPolicy;
|
this.retentionPolicy = retentionPolicy;
|
||||||
|
this.appAcls = appAcls;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ApplicationId getApplicationId() {
|
public ApplicationId getApplicationId() {
|
||||||
|
@ -54,4 +60,8 @@ public class LogAggregatorAppStartedEvent extends LogAggregatorEvent {
|
||||||
return this.user;
|
return this.user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<ApplicationAccessType, String> getApplicationAcls() {
|
||||||
|
return this.appAcls;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
package org.apache.hadoop.yarn.server.nodemanager.webapp;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.yarn.server.nodemanager.webapp.NMWebParams.CONTAINER_ID;
|
||||||
|
import static org.apache.hadoop.yarn.server.nodemanager.webapp.NMWebParams.NM_NODENAME;
|
||||||
|
import static org.apache.hadoop.yarn.server.nodemanager.webapp.NMWebParams.ENTITY_STRING;
|
||||||
|
import static org.apache.hadoop.yarn.server.nodemanager.webapp.NMWebParams.APP_OWNER;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.fs.Path;
|
||||||
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
|
import org.apache.hadoop.yarn.api.records.ApplicationAccessType;
|
||||||
|
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
||||||
|
import org.apache.hadoop.yarn.api.records.ContainerId;
|
||||||
|
import org.apache.hadoop.yarn.api.records.NodeId;
|
||||||
|
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.AggregatedLogFormat;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.LogAggregationService;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.AggregatedLogFormat.LogKey;
|
||||||
|
import org.apache.hadoop.yarn.server.security.ApplicationACLsManager;
|
||||||
|
import org.apache.hadoop.yarn.util.ConverterUtils;
|
||||||
|
import org.apache.hadoop.yarn.webapp.view.HtmlBlock;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
|
public class AggregatedLogsBlock extends HtmlBlock {
|
||||||
|
|
||||||
|
private final Configuration conf;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
AggregatedLogsBlock(Configuration conf) {
|
||||||
|
this.conf = conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void render(Block html) {
|
||||||
|
ContainerId containerId = verifyAndGetContainerId(html);
|
||||||
|
NodeId nodeId = verifyAndGetNodeId(html);
|
||||||
|
String appOwner = verifyAndGetAppOwner(html);
|
||||||
|
if (containerId == null || nodeId == null || appOwner == null
|
||||||
|
|| appOwner.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationId applicationId =
|
||||||
|
containerId.getApplicationAttemptId().getApplicationId();
|
||||||
|
String logEntity = $(ENTITY_STRING);
|
||||||
|
if (logEntity == null || logEntity.isEmpty()) {
|
||||||
|
logEntity = containerId.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
Path remoteRootLogDir =
|
||||||
|
new Path(conf.get(YarnConfiguration.NM_REMOTE_APP_LOG_DIR,
|
||||||
|
YarnConfiguration.DEFAULT_NM_REMOTE_APP_LOG_DIR));
|
||||||
|
AggregatedLogFormat.LogReader reader = null;
|
||||||
|
try {
|
||||||
|
reader =
|
||||||
|
new AggregatedLogFormat.LogReader(conf,
|
||||||
|
LogAggregationService.getRemoteNodeLogFileForApp(
|
||||||
|
remoteRootLogDir, applicationId, appOwner, nodeId,
|
||||||
|
LogAggregationService.getRemoteNodeLogDirSuffix(conf)));
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
// ACLs not available till the log file is opened.
|
||||||
|
html.h1()
|
||||||
|
._("Logs not available for "
|
||||||
|
+ logEntity
|
||||||
|
+ ". Aggregation may not be complete, "
|
||||||
|
+ "Check back later or try the nodemanager on "
|
||||||
|
+ nodeId)._();
|
||||||
|
return;
|
||||||
|
} catch (IOException e) {
|
||||||
|
html.h1()._("Error getting logs for " + logEntity)._();
|
||||||
|
LOG.error("Error getting logs for " + logEntity, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String owner = null;
|
||||||
|
Map<ApplicationAccessType, String> appAcls = null;
|
||||||
|
try {
|
||||||
|
owner = reader.getApplicationOwner();
|
||||||
|
appAcls = reader.getApplicationAcls();
|
||||||
|
} catch (IOException e) {
|
||||||
|
html.h1()._("Error getting logs for " + logEntity)._();
|
||||||
|
LOG.error("Error getting logs for " + logEntity, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ApplicationACLsManager aclsManager = new ApplicationACLsManager(conf);
|
||||||
|
aclsManager.addApplication(applicationId, appAcls);
|
||||||
|
|
||||||
|
String remoteUser = request().getRemoteUser();
|
||||||
|
UserGroupInformation callerUGI = null;
|
||||||
|
if (remoteUser != null) {
|
||||||
|
callerUGI = UserGroupInformation.createRemoteUser(remoteUser);
|
||||||
|
}
|
||||||
|
if (callerUGI != null
|
||||||
|
&& !aclsManager.checkAccess(callerUGI, ApplicationAccessType.VIEW_APP,
|
||||||
|
owner, applicationId)) {
|
||||||
|
html.h1()
|
||||||
|
._("User [" + remoteUser
|
||||||
|
+ "] is not authorized to view the logs for " + logEntity)._();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataInputStream valueStream;
|
||||||
|
LogKey key = new LogKey();
|
||||||
|
try {
|
||||||
|
valueStream = reader.next(key);
|
||||||
|
while (valueStream != null
|
||||||
|
&& !key.toString().equals(containerId.toString())) {
|
||||||
|
valueStream = reader.next(key);
|
||||||
|
}
|
||||||
|
if (valueStream == null) {
|
||||||
|
html.h1()._(
|
||||||
|
"Logs not available for " + logEntity
|
||||||
|
+ ". Could be caused by the rentention policy")._();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
writer().write("<pre>");
|
||||||
|
AggregatedLogFormat.LogReader.readAcontainerLogs(valueStream, writer());
|
||||||
|
writer().write("</pre>");
|
||||||
|
return;
|
||||||
|
} catch (IOException e) {
|
||||||
|
html.h1()._("Error getting logs for " + logEntity)._();
|
||||||
|
LOG.error("Error getting logs for " + logEntity, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ContainerId verifyAndGetContainerId(Block html) {
|
||||||
|
String containerIdStr = $(CONTAINER_ID);
|
||||||
|
if (containerIdStr == null || containerIdStr.isEmpty()) {
|
||||||
|
html.h1()._("Cannot get container logs without a ContainerId")._();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ContainerId containerId = null;
|
||||||
|
try {
|
||||||
|
containerId = ConverterUtils.toContainerId(containerIdStr);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
html.h1()
|
||||||
|
._("Cannot get container logs for invalid containerId: "
|
||||||
|
+ containerIdStr)._();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return containerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NodeId verifyAndGetNodeId(Block html) {
|
||||||
|
String nodeIdStr = $(NM_NODENAME);
|
||||||
|
if (nodeIdStr == null || nodeIdStr.isEmpty()) {
|
||||||
|
html.h1()._("Cannot get container logs without a NodeId")._();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
NodeId nodeId = null;
|
||||||
|
try {
|
||||||
|
nodeId = ConverterUtils.toNodeId(nodeIdStr);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
html.h1()._("Cannot get container logs. Invalid nodeId: " + nodeIdStr)
|
||||||
|
._();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return nodeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String verifyAndGetAppOwner(Block html) {
|
||||||
|
String appOwner = $(APP_OWNER);
|
||||||
|
if (appOwner == null || appOwner.isEmpty()) {
|
||||||
|
html.h1()._("Cannot get container logs without an app owner")._();
|
||||||
|
}
|
||||||
|
return appOwner;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.hadoop.yarn.server.nodemanager.webapp;
|
||||||
|
|
||||||
|
import org.apache.hadoop.yarn.webapp.view.HtmlBlock;
|
||||||
|
|
||||||
|
public class AggregatedLogsNavBlock extends HtmlBlock implements NMWebParams {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void render(Block html) {
|
||||||
|
html
|
||||||
|
.div("#nav")
|
||||||
|
.h3()._("Logs")._() //
|
||||||
|
._()
|
||||||
|
.div("#themeswitcher")._();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package org.apache.hadoop.yarn.server.nodemanager.webapp;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.yarn.server.nodemanager.webapp.NMWebParams.CONTAINER_ID;
|
||||||
|
import static org.apache.hadoop.yarn.server.nodemanager.webapp.NMWebParams.ENTITY_STRING;
|
||||||
|
import static org.apache.hadoop.yarn.util.StringHelper.join;
|
||||||
|
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.ACCORDION;
|
||||||
|
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.ACCORDION_ID;
|
||||||
|
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.THEMESWITCHER_ID;
|
||||||
|
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.initID;
|
||||||
|
|
||||||
|
|
||||||
|
import org.apache.hadoop.yarn.webapp.SubView;
|
||||||
|
|
||||||
|
|
||||||
|
public class AggregatedLogsPage extends NMView {
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see org.apache.hadoop.yarn.server.nodemanager.webapp.NMView#preHead(org.apache.hadoop.yarn.webapp.hamlet.Hamlet.HTML)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void preHead(Page.HTML<_> html) {
|
||||||
|
String logEntity = $(ENTITY_STRING);
|
||||||
|
if (logEntity == null || logEntity.isEmpty()) {
|
||||||
|
logEntity = $(CONTAINER_ID);
|
||||||
|
}
|
||||||
|
if (logEntity == null || logEntity.isEmpty()) {
|
||||||
|
logEntity = "UNKNOWN";
|
||||||
|
}
|
||||||
|
set(TITLE, join("Logs for ", logEntity));
|
||||||
|
set(ACCORDION_ID, "nav");
|
||||||
|
set(initID(ACCORDION, "nav"), "{autoHeight:false, active:0}");
|
||||||
|
set(THEMESWITCHER_ID, "themeswitcher");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<? extends SubView> content() {
|
||||||
|
return AggregatedLogsBlock.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<? extends SubView> nav() {
|
||||||
|
return AggregatedLogsNavBlock.class;
|
||||||
|
}
|
||||||
|
}
|
|
@ -91,7 +91,8 @@ public class AllContainersPage extends NMView {
|
||||||
._()
|
._()
|
||||||
.td()._(container.getContainerState())._()
|
.td()._(container.getContainerState())._()
|
||||||
.td()
|
.td()
|
||||||
.a(url("containerlogs", containerIdStr), "logs")._()
|
.a(url("containerlogs", containerIdStr, container.getUser()),
|
||||||
|
"logs")._()
|
||||||
._();
|
._();
|
||||||
}
|
}
|
||||||
tableBody._()._()._();
|
tableBody._()._()._();
|
||||||
|
|
|
@ -18,9 +18,17 @@
|
||||||
|
|
||||||
package org.apache.hadoop.yarn.server.nodemanager.webapp;
|
package org.apache.hadoop.yarn.server.nodemanager.webapp;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.yarn.server.nodemanager.webapp.NMWebParams.CONTAINER_ID;
|
||||||
|
import static org.apache.hadoop.yarn.util.StringHelper.join;
|
||||||
|
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.ACCORDION;
|
||||||
|
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.ACCORDION_ID;
|
||||||
|
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.THEMESWITCHER_ID;
|
||||||
|
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.initID;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -30,32 +38,53 @@ import org.apache.hadoop.fs.LocalDirAllocator;
|
||||||
import org.apache.hadoop.fs.Path;
|
import org.apache.hadoop.fs.Path;
|
||||||
import org.apache.hadoop.security.UserGroupInformation;
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
import org.apache.hadoop.util.StringUtils;
|
import org.apache.hadoop.util.StringUtils;
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationAccessType;
|
import org.apache.hadoop.yarn.api.records.ApplicationAccessType;
|
||||||
|
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
||||||
import org.apache.hadoop.yarn.api.records.ContainerId;
|
import org.apache.hadoop.yarn.api.records.ContainerId;
|
||||||
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.Context;
|
import org.apache.hadoop.yarn.server.nodemanager.Context;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.application.Application;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.application.Application;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.Container;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.Container;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.ContainerState;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.ContainerState;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.launcher.ContainerLaunch;
|
||||||
import org.apache.hadoop.yarn.server.security.ApplicationACLsManager;
|
import org.apache.hadoop.yarn.server.security.ApplicationACLsManager;
|
||||||
import org.apache.hadoop.yarn.util.ConverterUtils;
|
import org.apache.hadoop.yarn.util.ConverterUtils;
|
||||||
import org.apache.hadoop.yarn.webapp.SubView;
|
import org.apache.hadoop.yarn.webapp.SubView;
|
||||||
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet;
|
|
||||||
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.DIV;
|
|
||||||
import org.apache.hadoop.yarn.webapp.view.HtmlBlock;
|
import org.apache.hadoop.yarn.webapp.view.HtmlBlock;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
public class ContainerLogsPage extends NMView {
|
public class ContainerLogsPage extends NMView {
|
||||||
|
|
||||||
|
public static final String REDIRECT_URL = "redirect.url";
|
||||||
|
|
||||||
|
@Override protected void preHead(Page.HTML<_> html) {
|
||||||
|
String redirectUrl = $(REDIRECT_URL);
|
||||||
|
if (redirectUrl == null || redirectUrl.isEmpty()) {
|
||||||
|
set(TITLE, join("Logs for ", $(CONTAINER_ID)));
|
||||||
|
html.meta_http("refresh", "10");
|
||||||
|
} else {
|
||||||
|
if (redirectUrl.equals("false")) {
|
||||||
|
set(TITLE, join("Failed redirect for ", $(CONTAINER_ID)));
|
||||||
|
//Error getting redirect url. Fall through.
|
||||||
|
} else {
|
||||||
|
set(TITLE, join("Redirecting to log server for ", $(CONTAINER_ID)));
|
||||||
|
html.meta_http("refresh", "1; url=" + redirectUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set(ACCORDION_ID, "nav");
|
||||||
|
set(initID(ACCORDION, "nav"), "{autoHeight:false, active:0}");
|
||||||
|
set(THEMESWITCHER_ID, "themeswitcher");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Class<? extends SubView> content() {
|
protected Class<? extends SubView> content() {
|
||||||
return ContainersLogsBlock.class;
|
return ContainersLogsBlock.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ContainersLogsBlock extends HtmlBlock implements
|
public static class ContainersLogsBlock extends HtmlBlock implements
|
||||||
NMWebParams {
|
NMWebParams {
|
||||||
|
|
||||||
private final Configuration conf;
|
private final Configuration conf;
|
||||||
private final LocalDirAllocator logsSelector;
|
private final LocalDirAllocator logsSelector;
|
||||||
private final Context nmContext;
|
private final Context nmContext;
|
||||||
|
@ -72,13 +101,19 @@ public class ContainerLogsPage extends NMView {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void render(Block html) {
|
protected void render(Block html) {
|
||||||
DIV<Hamlet> div = html.div("#content");
|
|
||||||
|
|
||||||
|
String redirectUrl = $(REDIRECT_URL);
|
||||||
|
if (redirectUrl !=null && redirectUrl.equals("false")) {
|
||||||
|
html.h1("Failed while trying to construct the redirect url to the log" +
|
||||||
|
" server. Log Server url may not be configured");
|
||||||
|
//Intentional fallthrough.
|
||||||
|
}
|
||||||
|
|
||||||
ContainerId containerId;
|
ContainerId containerId;
|
||||||
try {
|
try {
|
||||||
containerId = ConverterUtils.toContainerId($(CONTAINER_ID));
|
containerId = ConverterUtils.toContainerId($(CONTAINER_ID));
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
div.h1("Invalid containerId " + $(CONTAINER_ID))._();
|
html.h1("Invalid containerId " + $(CONTAINER_ID));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,19 +123,28 @@ public class ContainerLogsPage extends NMView {
|
||||||
applicationId);
|
applicationId);
|
||||||
Container container = this.nmContext.getContainers().get(containerId);
|
Container container = this.nmContext.getContainers().get(containerId);
|
||||||
|
|
||||||
if (application == null || container == null) {
|
if (application == null) {
|
||||||
div.h1(
|
html.h1(
|
||||||
"Unknown container. Container is either not yet running or "
|
"Unknown container. Container either has not started or "
|
||||||
+ "has already completed or "
|
+ "has already completed or "
|
||||||
+ "doesn't belong to this node at all.")._();
|
+ "doesn't belong to this node at all.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (container == null) {
|
||||||
|
// Container may have alerady completed, but logs not aggregated yet.
|
||||||
|
printLogs(html, containerId, applicationId, application);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (EnumSet.of(ContainerState.NEW, ContainerState.LOCALIZING,
|
if (EnumSet.of(ContainerState.NEW, ContainerState.LOCALIZING,
|
||||||
ContainerState.LOCALIZING).contains(container.getContainerState())) {
|
ContainerState.LOCALIZED).contains(container.getContainerState())) {
|
||||||
div.h1("Container is not yet running. Current state is "
|
html.h1("Container is not yet running. Current state is "
|
||||||
+ container.getContainerState())
|
+ container.getContainerState());
|
||||||
._();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (container.getContainerState() == ContainerState.LOCALIZATION_FAILED) {
|
||||||
|
html.h1("Container wasn't started. Localization failed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,103 +152,144 @@ public class ContainerLogsPage extends NMView {
|
||||||
ContainerState.EXITED_WITH_FAILURE,
|
ContainerState.EXITED_WITH_FAILURE,
|
||||||
ContainerState.EXITED_WITH_SUCCESS).contains(
|
ContainerState.EXITED_WITH_SUCCESS).contains(
|
||||||
container.getContainerState())) {
|
container.getContainerState())) {
|
||||||
|
printLogs(html, containerId, applicationId, application);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (EnumSet.of(ContainerState.KILLING,
|
||||||
|
ContainerState.CONTAINER_CLEANEDUP_AFTER_KILL,
|
||||||
|
ContainerState.CONTAINER_RESOURCES_CLEANINGUP).contains(
|
||||||
|
container.getContainerState())) {
|
||||||
|
//Container may have generated some logs before being killed.
|
||||||
|
printLogs(html, containerId, applicationId, application);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (container.getContainerState().equals(ContainerState.DONE)) {
|
||||||
|
// Prev state unknown. Logs may be available.
|
||||||
|
printLogs(html, containerId, applicationId, application);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
html.h1("Container is no longer running...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check for the authorization.
|
private void printLogs(Block html, ContainerId containerId,
|
||||||
String remoteUser = request().getRemoteUser();
|
ApplicationId applicationId, Application application) {
|
||||||
UserGroupInformation callerUGI = null;
|
// Check for the authorization.
|
||||||
if (remoteUser != null) {
|
String remoteUser = request().getRemoteUser();
|
||||||
callerUGI = UserGroupInformation.createRemoteUser(remoteUser);
|
UserGroupInformation callerUGI = null;
|
||||||
}
|
|
||||||
if (callerUGI != null && !this.aclsManager.checkAccess(callerUGI,
|
if (remoteUser != null) {
|
||||||
ApplicationAccessType.VIEW_APP, application.getUser(),
|
callerUGI = UserGroupInformation.createRemoteUser(remoteUser);
|
||||||
applicationId)) {
|
}
|
||||||
div.h1(
|
if (callerUGI != null
|
||||||
"You (User " + remoteUser
|
&& !this.aclsManager.checkAccess(callerUGI,
|
||||||
+ ") are not authorized to view the logs for application "
|
ApplicationAccessType.VIEW_APP, application.getUser(),
|
||||||
+ applicationId)._();
|
applicationId)) {
|
||||||
|
html.h1(
|
||||||
|
"User [" + remoteUser
|
||||||
|
+ "] is not authorized to view the logs for application "
|
||||||
|
+ applicationId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$(CONTAINER_LOG_TYPE).isEmpty()) {
|
||||||
|
File logFile = null;
|
||||||
|
try {
|
||||||
|
logFile =
|
||||||
|
new File(this.logsSelector
|
||||||
|
.getLocalPathToRead(
|
||||||
|
ContainerLaunch.getRelativeContainerLogDir(
|
||||||
|
applicationId.toString(), containerId.toString())
|
||||||
|
+ Path.SEPARATOR + $(CONTAINER_LOG_TYPE), this.conf)
|
||||||
|
.toUri().getPath());
|
||||||
|
} catch (Exception e) {
|
||||||
|
html.h1("Cannot find this log on the local disk.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
long start =
|
||||||
if (!$(CONTAINER_LOG_TYPE).isEmpty()) {
|
$("start").isEmpty() ? -4 * 1024 : Long.parseLong($("start"));
|
||||||
File logFile = null;
|
start = start < 0 ? logFile.length() + start : start;
|
||||||
try {
|
start = start < 0 ? 0 : start;
|
||||||
logFile =
|
long end =
|
||||||
new File(this.logsSelector
|
$("end").isEmpty() ? logFile.length() : Long.parseLong($("end"));
|
||||||
.getLocalPathToRead(
|
end = end < 0 ? logFile.length() + end : end;
|
||||||
ConverterUtils.toString(
|
end = end < 0 ? logFile.length() : end;
|
||||||
applicationId)
|
if (start > end) {
|
||||||
+ Path.SEPARATOR + $(CONTAINER_ID)
|
html.h1("Invalid start and end values. Start: [" + start + "]"
|
||||||
+ Path.SEPARATOR
|
+ ", end[" + end + "]");
|
||||||
+ $(CONTAINER_LOG_TYPE), this.conf).toUri()
|
return;
|
||||||
.getPath());
|
} else {
|
||||||
} catch (Exception e) {
|
InputStreamReader reader = null;
|
||||||
div.h1("Cannot find this log on the local disk.")._();
|
|
||||||
}
|
|
||||||
div.h1(logFile == null ? "Unknown LogFile" : logFile.getName());
|
|
||||||
long start =
|
|
||||||
$("start").isEmpty() ? -4 * 1024 : Long.parseLong($("start"));
|
|
||||||
start = start < 0 ? logFile.length() + start : start;
|
|
||||||
start = start < 0 ? 0 : start;
|
|
||||||
long end =
|
|
||||||
$("end").isEmpty() ? logFile.length() : Long
|
|
||||||
.parseLong($("end"));
|
|
||||||
end = end < 0 ? logFile.length() + end : end;
|
|
||||||
end = end < 0 ? logFile.length() : end;
|
|
||||||
if (start > end) {
|
|
||||||
writer().write("Invalid start and end values!");
|
|
||||||
} else {
|
|
||||||
try {
|
try {
|
||||||
long toRead = end - start;
|
long toRead = end - start;
|
||||||
if (toRead < logFile.length()) {
|
if (toRead < logFile.length()) {
|
||||||
div._("Showing " + toRead + " bytes. Click ")
|
html.p()._("Showing " + toRead + " bytes. Click ")
|
||||||
.a(url("containerlogs", $(CONTAINER_ID),
|
.a(url("containerlogs", $(CONTAINER_ID), $(APP_OWNER),
|
||||||
logFile.getName()), "here")
|
logFile.getName(), "?start=0"), "here").
|
||||||
._(" for full log").br()._();
|
_(" for full log")._();
|
||||||
}
|
}
|
||||||
// TODO: Use secure IO Utils to avoid symlink attacks.
|
// TODO: Use secure IO Utils to avoid symlink attacks.
|
||||||
//TODO Fix findBugs close warning along with IOUtils change
|
// TODO Fix findBugs close warning along with IOUtils change
|
||||||
FileReader reader = new FileReader(logFile);
|
reader = new FileReader(logFile);
|
||||||
char[] cbuf = new char[65536];
|
int bufferSize = 65536;
|
||||||
reader.skip(start);
|
char[] cbuf = new char[bufferSize];
|
||||||
int len = 0;
|
|
||||||
int totalRead = 0;
|
long skipped = 0;
|
||||||
writer().write("<pre>");
|
long totalSkipped = 0;
|
||||||
while ((len = reader.read(cbuf, 0, (int) toRead)) > 0
|
while (totalSkipped < start) {
|
||||||
&& totalRead < (end - start)) {
|
skipped = reader.skip(start - totalSkipped);
|
||||||
writer().write(cbuf, 0, len); // TODO: HTMl Quoting?
|
totalSkipped += skipped;
|
||||||
totalRead += len;
|
|
||||||
toRead = toRead - totalRead;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int len = 0;
|
||||||
|
int currentToRead = toRead > bufferSize ? bufferSize : (int) toRead;
|
||||||
|
writer().write("<pre>");
|
||||||
|
|
||||||
|
while ((len = reader.read(cbuf, 0, currentToRead)) > 0
|
||||||
|
&& toRead > 0) {
|
||||||
|
writer().write(cbuf, 0, len); // TODO: HTMl Quoting?
|
||||||
|
toRead = toRead - len;
|
||||||
|
currentToRead = toRead > bufferSize ? bufferSize : (int) toRead;
|
||||||
|
}
|
||||||
|
|
||||||
reader.close();
|
reader.close();
|
||||||
writer().write("</pre>");
|
writer().write("</pre>");
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
writer().write(
|
html.h1("Exception reading log-file. Log file was likely aggregated. "
|
||||||
"Exception reading log-file "
|
+ StringUtils.stringifyException(e));
|
||||||
+ StringUtils.stringifyException(e));
|
} finally {
|
||||||
}
|
if (reader != null) {
|
||||||
}
|
try {
|
||||||
div._();
|
reader.close();
|
||||||
} else {
|
} catch (IOException e) {
|
||||||
// Just print out the log-types
|
// Ignore
|
||||||
List<File> containerLogsDirs =
|
}
|
||||||
getContainerLogDirs(this.conf, containerId);
|
|
||||||
for (File containerLogsDir : containerLogsDirs) {
|
|
||||||
for (File logFile : containerLogsDir.listFiles()) {
|
|
||||||
div
|
|
||||||
.p()
|
|
||||||
.a(
|
|
||||||
url("containerlogs", $(CONTAINER_ID),
|
|
||||||
logFile.getName(), "?start=-4076"),
|
|
||||||
logFile.getName() + " : Total file length is "
|
|
||||||
+ logFile.length() + " bytes.")
|
|
||||||
._();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div._();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
div.h1("Container is no longer running..")._();
|
// Just print out the log-types
|
||||||
|
List<File> containerLogsDirs =
|
||||||
|
getContainerLogDirs(this.conf, containerId);
|
||||||
|
boolean foundLogFile = false;
|
||||||
|
for (File containerLogsDir : containerLogsDirs) {
|
||||||
|
for (File logFile : containerLogsDir.listFiles()) {
|
||||||
|
foundLogFile = true;
|
||||||
|
html.p()
|
||||||
|
.a(url("containerlogs", $(CONTAINER_ID), $(APP_OWNER),
|
||||||
|
logFile.getName(), "?start=-4096"),
|
||||||
|
logFile.getName() + " : Total file length is "
|
||||||
|
+ logFile.length() + " bytes.")._();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!foundLogFile) {
|
||||||
|
html.h1("No logs available for container " + containerId.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<File>
|
static List<File>
|
||||||
|
@ -222,6 +307,5 @@ public class ContainerLogsPage extends NMView {
|
||||||
}
|
}
|
||||||
return containerLogDirs;
|
return containerLogDirs;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,8 @@ public class ContainerPage extends NMView implements NMWebParams {
|
||||||
._("User", container.getUser())
|
._("User", container.getUser())
|
||||||
._("TotalMemoryNeeded",
|
._("TotalMemoryNeeded",
|
||||||
container.getLaunchContext().getResource().getMemory())
|
container.getLaunchContext().getResource().getMemory())
|
||||||
._("logs", ujoin("containerlogs", $(CONTAINER_ID)), "Link to logs");
|
._("logs", ujoin("containerlogs", $(CONTAINER_ID), container.getUser()),
|
||||||
|
"Link to logs");
|
||||||
html._(InfoBlock.class);
|
html._(InfoBlock.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,15 +21,27 @@ package org.apache.hadoop.yarn.server.nodemanager.webapp;
|
||||||
import static org.apache.hadoop.yarn.util.StringHelper.join;
|
import static org.apache.hadoop.yarn.util.StringHelper.join;
|
||||||
|
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
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.server.nodemanager.Context;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.application.Application;
|
||||||
|
import org.apache.hadoop.yarn.util.ConverterUtils;
|
||||||
import org.apache.hadoop.yarn.webapp.Controller;
|
import org.apache.hadoop.yarn.webapp.Controller;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
public class NMController extends Controller implements NMWebParams {
|
public class NMController extends Controller implements NMWebParams {
|
||||||
|
|
||||||
|
private Context nmContext;
|
||||||
|
private Configuration nmConf;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public NMController(Configuration nmConf, RequestContext requestContext) {
|
public NMController(Configuration nmConf, RequestContext requestContext,
|
||||||
|
Context nmContext) {
|
||||||
super(requestContext);
|
super(requestContext);
|
||||||
|
this.nmContext = nmContext;
|
||||||
|
this.nmConf = nmConf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -63,6 +75,29 @@ public class NMController extends Controller implements NMWebParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void logs() {
|
public void logs() {
|
||||||
|
String containerIdStr = $(CONTAINER_ID);
|
||||||
|
ContainerId containerId = null;
|
||||||
|
try {
|
||||||
|
containerId = ConverterUtils.toContainerId(containerIdStr);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
render(ContainerLogsPage.class);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ApplicationId appId =
|
||||||
|
containerId.getApplicationAttemptId().getApplicationId();
|
||||||
|
Application app = nmContext.getApplications().get(appId);
|
||||||
|
if (app == null) {
|
||||||
|
String logServerUrl = nmConf.get(YarnConfiguration.YARN_LOG_SERVER_URL);
|
||||||
|
String redirectUrl = null;
|
||||||
|
if (logServerUrl == null || logServerUrl.isEmpty()) {
|
||||||
|
redirectUrl = "false";
|
||||||
|
} else {
|
||||||
|
redirectUrl =
|
||||||
|
url(logServerUrl, nmContext.getNodeId().toString(), containerIdStr,
|
||||||
|
containerIdStr, $(APP_OWNER));
|
||||||
|
}
|
||||||
|
set(ContainerLogsPage.REDIRECT_URL, redirectUrl);
|
||||||
|
}
|
||||||
render(ContainerLogsPage.class);
|
render(ContainerLogsPage.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,4 +23,6 @@ public interface NMWebParams {
|
||||||
String APPLICATION_ID = "nm.appId";
|
String APPLICATION_ID = "nm.appId";
|
||||||
String CONTAINER_ID = "nm.containerId";
|
String CONTAINER_ID = "nm.containerId";
|
||||||
String CONTAINER_LOG_TYPE= "nm.containerLogType";
|
String CONTAINER_LOG_TYPE= "nm.containerLogType";
|
||||||
|
String ENTITY_STRING = "nm.entityString";
|
||||||
|
String APP_OWNER = "nm.appOwner";
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,8 @@ public class WebServer extends AbstractService {
|
||||||
"application");
|
"application");
|
||||||
route(pajoin("/container", CONTAINER_ID), NMController.class,
|
route(pajoin("/container", CONTAINER_ID), NMController.class,
|
||||||
"container");
|
"container");
|
||||||
route(pajoin("/containerlogs", CONTAINER_ID, CONTAINER_LOG_TYPE),
|
route(
|
||||||
|
pajoin("/containerlogs", CONTAINER_ID, APP_OWNER, CONTAINER_LOG_TYPE),
|
||||||
NMController.class, "logs");
|
NMController.class, "logs");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.apache.hadoop.fs.Path;
|
import org.apache.hadoop.fs.Path;
|
||||||
import org.apache.hadoop.yarn.api.records.ContainerId;
|
import org.apache.hadoop.yarn.api.records.ContainerId;
|
||||||
|
import org.apache.hadoop.yarn.event.AsyncDispatcher;
|
||||||
import org.apache.hadoop.yarn.factories.RecordFactory;
|
import org.apache.hadoop.yarn.factories.RecordFactory;
|
||||||
import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider;
|
import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider;
|
||||||
import org.apache.hadoop.yarn.server.security.ApplicationACLsManager;
|
import org.apache.hadoop.yarn.server.security.ApplicationACLsManager;
|
||||||
|
@ -56,8 +57,6 @@ public class DummyContainerManager extends ContainerManagerImpl {
|
||||||
|
|
||||||
private static final Log LOG = LogFactory
|
private static final Log LOG = LogFactory
|
||||||
.getLog(DummyContainerManager.class);
|
.getLog(DummyContainerManager.class);
|
||||||
|
|
||||||
private static final RecordFactory recordFactory = RecordFactoryProvider.getRecordFactory(null);
|
|
||||||
|
|
||||||
public DummyContainerManager(Context context, ContainerExecutor exec,
|
public DummyContainerManager(Context context, ContainerExecutor exec,
|
||||||
DeletionService deletionContext, NodeStatusUpdater nodeStatusUpdater,
|
DeletionService deletionContext, NodeStatusUpdater nodeStatusUpdater,
|
||||||
|
@ -150,7 +149,8 @@ public class DummyContainerManager extends ContainerManagerImpl {
|
||||||
@Override
|
@Override
|
||||||
protected LogAggregationService createLogAggregationService(Context context,
|
protected LogAggregationService createLogAggregationService(Context context,
|
||||||
DeletionService deletionService) {
|
DeletionService deletionService) {
|
||||||
return new LogAggregationService(context, deletionService) {
|
return new LogAggregationService(new AsyncDispatcher(), context,
|
||||||
|
deletionService) {
|
||||||
@Override
|
@Override
|
||||||
public void handle(LogAggregatorEvent event) {
|
public void handle(LogAggregatorEvent event) {
|
||||||
switch (event.getType()) {
|
switch (event.getType()) {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import org.apache.hadoop.yarn.api.records.ContainerId;
|
||||||
import org.apache.hadoop.yarn.api.records.ContainerLaunchContext;
|
import org.apache.hadoop.yarn.api.records.ContainerLaunchContext;
|
||||||
import org.apache.hadoop.yarn.event.DrainDispatcher;
|
import org.apache.hadoop.yarn.event.DrainDispatcher;
|
||||||
import org.apache.hadoop.yarn.event.EventHandler;
|
import org.apache.hadoop.yarn.event.EventHandler;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.Context;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.AuxServicesEvent;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.AuxServicesEvent;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.AuxServicesEventType;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.AuxServicesEventType;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.Container;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.Container;
|
||||||
|
@ -351,6 +352,7 @@ public class TestApplication {
|
||||||
final EventHandler<LogAggregatorEvent> logAggregationBus;
|
final EventHandler<LogAggregatorEvent> logAggregationBus;
|
||||||
final String user;
|
final String user;
|
||||||
final List<Container> containers;
|
final List<Container> containers;
|
||||||
|
final Context context;
|
||||||
|
|
||||||
final ApplicationId appId;
|
final ApplicationId appId;
|
||||||
final Application app;
|
final Application app;
|
||||||
|
@ -373,11 +375,13 @@ public class TestApplication {
|
||||||
dispatcher.register(ContainerEventType.class, containerBus);
|
dispatcher.register(ContainerEventType.class, containerBus);
|
||||||
dispatcher.register(LogAggregatorEventType.class, logAggregationBus);
|
dispatcher.register(LogAggregatorEventType.class, logAggregationBus);
|
||||||
|
|
||||||
|
context = mock(Context.class);
|
||||||
|
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.appId = BuilderUtils.newApplicationId(timestamp, id);
|
this.appId = BuilderUtils.newApplicationId(timestamp, id);
|
||||||
|
|
||||||
app = new ApplicationImpl(dispatcher, new ApplicationACLsManager(
|
app = new ApplicationImpl(dispatcher, new ApplicationACLsManager(
|
||||||
new Configuration()), this.user, appId, null);
|
new Configuration()), this.user, appId, null, context);
|
||||||
containers = new ArrayList<Container>();
|
containers = new ArrayList<Container>();
|
||||||
for (int i = 0; i < numContainers; i++) {
|
for (int i = 0; i < numContainers; i++) {
|
||||||
containers.add(createMockedContainer(this.appId, i));
|
containers.add(createMockedContainer(this.appId, i));
|
||||||
|
|
|
@ -18,6 +18,12 @@
|
||||||
|
|
||||||
package org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation;
|
package org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static junit.framework.Assert.assertEquals;
|
||||||
|
import static junit.framework.Assert.assertTrue;
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -28,8 +34,10 @@ import java.io.Writer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import junit.framework.Assert;
|
import junit.framework.Assert;
|
||||||
|
|
||||||
|
@ -41,6 +49,7 @@ import org.apache.hadoop.io.DataOutputBuffer;
|
||||||
import org.apache.hadoop.yarn.api.protocolrecords.StartContainerRequest;
|
import org.apache.hadoop.yarn.api.protocolrecords.StartContainerRequest;
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
|
import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
||||||
|
import org.apache.hadoop.yarn.api.records.ApplicationAccessType;
|
||||||
import org.apache.hadoop.yarn.api.records.ContainerId;
|
import org.apache.hadoop.yarn.api.records.ContainerId;
|
||||||
import org.apache.hadoop.yarn.api.records.ContainerLaunchContext;
|
import org.apache.hadoop.yarn.api.records.ContainerLaunchContext;
|
||||||
import org.apache.hadoop.yarn.api.records.ContainerState;
|
import org.apache.hadoop.yarn.api.records.ContainerState;
|
||||||
|
@ -50,11 +59,15 @@ import org.apache.hadoop.yarn.api.records.LocalResourceVisibility;
|
||||||
import org.apache.hadoop.yarn.api.records.Resource;
|
import org.apache.hadoop.yarn.api.records.Resource;
|
||||||
import org.apache.hadoop.yarn.api.records.URL;
|
import org.apache.hadoop.yarn.api.records.URL;
|
||||||
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
||||||
|
import org.apache.hadoop.yarn.event.DrainDispatcher;
|
||||||
|
import org.apache.hadoop.yarn.event.EventHandler;
|
||||||
import org.apache.hadoop.yarn.factories.RecordFactory;
|
import org.apache.hadoop.yarn.factories.RecordFactory;
|
||||||
import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider;
|
import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.CMgrCompletedAppsEvent;
|
import org.apache.hadoop.yarn.server.nodemanager.CMgrCompletedAppsEvent;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.DeletionService;
|
import org.apache.hadoop.yarn.server.nodemanager.DeletionService;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.BaseContainerManagerTest;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.BaseContainerManagerTest;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.application.ApplicationEvent;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.application.ApplicationEventType;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.AggregatedLogFormat.LogKey;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.AggregatedLogFormat.LogKey;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.AggregatedLogFormat.LogReader;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.AggregatedLogFormat.LogReader;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.event.LogAggregatorAppFinishedEvent;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.event.LogAggregatorAppFinishedEvent;
|
||||||
|
@ -62,12 +75,15 @@ import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.event.LogAggregatorContainerFinishedEvent;
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.logaggregation.event.LogAggregatorContainerFinishedEvent;
|
||||||
import org.apache.hadoop.yarn.util.BuilderUtils;
|
import org.apache.hadoop.yarn.util.BuilderUtils;
|
||||||
import org.apache.hadoop.yarn.util.ConverterUtils;
|
import org.apache.hadoop.yarn.util.ConverterUtils;
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
@Ignore
|
|
||||||
|
//@Ignore
|
||||||
public class TestLogAggregationService extends BaseContainerManagerTest {
|
public class TestLogAggregationService extends BaseContainerManagerTest {
|
||||||
|
|
||||||
|
private Map<ApplicationAccessType, String> acls = createAppAcls();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
LOG = LogFactory.getLog(TestLogAggregationService.class);
|
LOG = LogFactory.getLog(TestLogAggregationService.class);
|
||||||
}
|
}
|
||||||
|
@ -91,17 +107,25 @@ public class TestLogAggregationService extends BaseContainerManagerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public void testLocalFileDeletionAfterUpload() throws IOException {
|
public void testLocalFileDeletionAfterUpload() throws IOException {
|
||||||
this.delSrvc = new DeletionService(createContainerExecutor());
|
this.delSrvc = new DeletionService(createContainerExecutor());
|
||||||
this.delSrvc.init(conf);
|
this.delSrvc.init(conf);
|
||||||
this.conf.set(YarnConfiguration.NM_LOG_DIRS, localLogDir.getAbsolutePath());
|
this.conf.set(YarnConfiguration.NM_LOG_DIRS, localLogDir.getAbsolutePath());
|
||||||
this.conf.set(YarnConfiguration.NM_REMOTE_APP_LOG_DIR,
|
this.conf.set(YarnConfiguration.NM_REMOTE_APP_LOG_DIR,
|
||||||
this.remoteRootLogDir.getAbsolutePath());
|
this.remoteRootLogDir.getAbsolutePath());
|
||||||
|
this.conf.setBoolean(YarnConfiguration.NM_LOG_AGGREGATION_ENABLED, true);
|
||||||
|
|
||||||
|
DrainDispatcher dispatcher = createDispatcher();
|
||||||
|
EventHandler<ApplicationEvent> appEventHandler = mock(EventHandler.class);
|
||||||
|
dispatcher.register(ApplicationEventType.class, appEventHandler);
|
||||||
|
|
||||||
LogAggregationService logAggregationService =
|
LogAggregationService logAggregationService =
|
||||||
new LogAggregationService(this.context, this.delSrvc);
|
new LogAggregationService(dispatcher, this.context, this.delSrvc);
|
||||||
logAggregationService.init(this.conf);
|
logAggregationService.init(this.conf);
|
||||||
logAggregationService.start();
|
logAggregationService.start();
|
||||||
|
|
||||||
|
|
||||||
ApplicationId application1 = BuilderUtils.newApplicationId(1234, 1);
|
ApplicationId application1 = BuilderUtils.newApplicationId(1234, 1);
|
||||||
|
|
||||||
// AppLogDir should be created
|
// AppLogDir should be created
|
||||||
|
@ -111,13 +135,11 @@ public class TestLogAggregationService extends BaseContainerManagerTest {
|
||||||
logAggregationService
|
logAggregationService
|
||||||
.handle(new LogAggregatorAppStartedEvent(
|
.handle(new LogAggregatorAppStartedEvent(
|
||||||
application1, this.user, null,
|
application1, this.user, null,
|
||||||
ContainerLogsRetentionPolicy.ALL_CONTAINERS));
|
ContainerLogsRetentionPolicy.ALL_CONTAINERS, this.acls));
|
||||||
|
|
||||||
ApplicationAttemptId appAttemptId = recordFactory.newRecordInstance(ApplicationAttemptId.class);
|
ApplicationAttemptId appAttemptId =
|
||||||
appAttemptId.setApplicationId(application1);
|
BuilderUtils.newApplicationAttemptId(application1, 1);
|
||||||
appAttemptId.setAttemptId(1);
|
ContainerId container11 = BuilderUtils.newContainerId(appAttemptId, 1);
|
||||||
ContainerId container11 =
|
|
||||||
BuilderUtils.newContainerId(recordFactory, application1, appAttemptId, 1);
|
|
||||||
// Simulate log-file creation
|
// Simulate log-file creation
|
||||||
writeContainerLogs(app1LogDir, container11);
|
writeContainerLogs(app1LogDir, container11);
|
||||||
logAggregationService.handle(
|
logAggregationService.handle(
|
||||||
|
@ -128,6 +150,7 @@ public class TestLogAggregationService extends BaseContainerManagerTest {
|
||||||
|
|
||||||
logAggregationService.stop();
|
logAggregationService.stop();
|
||||||
|
|
||||||
|
|
||||||
String containerIdStr = ConverterUtils.toString(container11);
|
String containerIdStr = ConverterUtils.toString(container11);
|
||||||
File containerLogDir = new File(app1LogDir, containerIdStr);
|
File containerLogDir = new File(app1LogDir, containerIdStr);
|
||||||
for (String fileType : new String[] { "stdout", "stderr", "syslog" }) {
|
for (String fileType : new String[] { "stdout", "stderr", "syslog" }) {
|
||||||
|
@ -136,17 +159,37 @@ public class TestLogAggregationService extends BaseContainerManagerTest {
|
||||||
|
|
||||||
Assert.assertFalse(app1LogDir.exists());
|
Assert.assertFalse(app1LogDir.exists());
|
||||||
|
|
||||||
Assert.assertTrue(new File(logAggregationService
|
Path logFilePath =
|
||||||
.getRemoteNodeLogFileForApp(application1).toUri().getPath()).exists());
|
logAggregationService.getRemoteNodeLogFileForApp(application1,
|
||||||
|
this.user);
|
||||||
|
Assert.assertTrue("Log file [" + logFilePath + "] not found", new File(
|
||||||
|
logFilePath.toUri().getPath()).exists());
|
||||||
|
|
||||||
|
dispatcher.await();
|
||||||
|
ArgumentCaptor<ApplicationEvent> eventCaptor =
|
||||||
|
ArgumentCaptor.forClass(ApplicationEvent.class);
|
||||||
|
verify(appEventHandler).handle(eventCaptor.capture());
|
||||||
|
assertEquals(ApplicationEventType.APPLICATION_LOG_AGGREGATION_FINISHED,
|
||||||
|
eventCaptor.getValue().getType());
|
||||||
|
assertEquals(appAttemptId.getApplicationId(), eventCaptor.getValue()
|
||||||
|
.getApplicationID());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public void testNoContainerOnNode() {
|
public void testNoContainerOnNode() {
|
||||||
this.conf.set(YarnConfiguration.NM_LOG_DIRS, localLogDir.getAbsolutePath());
|
this.conf.set(YarnConfiguration.NM_LOG_DIRS, localLogDir.getAbsolutePath());
|
||||||
this.conf.set(YarnConfiguration.NM_REMOTE_APP_LOG_DIR,
|
this.conf.set(YarnConfiguration.NM_REMOTE_APP_LOG_DIR,
|
||||||
this.remoteRootLogDir.getAbsolutePath());
|
this.remoteRootLogDir.getAbsolutePath());
|
||||||
|
this.conf.setBoolean(YarnConfiguration.NM_LOG_AGGREGATION_ENABLED, true);
|
||||||
|
|
||||||
|
DrainDispatcher dispatcher = createDispatcher();
|
||||||
|
EventHandler<ApplicationEvent> appEventHandler = mock(EventHandler.class);
|
||||||
|
dispatcher.register(ApplicationEventType.class, appEventHandler);
|
||||||
|
|
||||||
LogAggregationService logAggregationService =
|
LogAggregationService logAggregationService =
|
||||||
new LogAggregationService(this.context, this.delSrvc);
|
new LogAggregationService(dispatcher, this.context, this.delSrvc);
|
||||||
logAggregationService.init(this.conf);
|
logAggregationService.init(this.conf);
|
||||||
logAggregationService.start();
|
logAggregationService.start();
|
||||||
|
|
||||||
|
@ -159,27 +202,43 @@ public class TestLogAggregationService extends BaseContainerManagerTest {
|
||||||
logAggregationService
|
logAggregationService
|
||||||
.handle(new LogAggregatorAppStartedEvent(
|
.handle(new LogAggregatorAppStartedEvent(
|
||||||
application1, this.user, null,
|
application1, this.user, null,
|
||||||
ContainerLogsRetentionPolicy.ALL_CONTAINERS));
|
ContainerLogsRetentionPolicy.ALL_CONTAINERS, this.acls));
|
||||||
|
|
||||||
logAggregationService.handle(new LogAggregatorAppFinishedEvent(
|
logAggregationService.handle(new LogAggregatorAppFinishedEvent(
|
||||||
application1));
|
application1));
|
||||||
|
|
||||||
logAggregationService.stop();
|
logAggregationService.stop();
|
||||||
|
|
||||||
Assert
|
Assert.assertFalse(new File(logAggregationService
|
||||||
.assertFalse(new File(logAggregationService
|
.getRemoteNodeLogFileForApp(application1, this.user).toUri().getPath())
|
||||||
.getRemoteNodeLogFileForApp(application1).toUri().getPath())
|
.exists());
|
||||||
.exists());
|
|
||||||
|
dispatcher.await();
|
||||||
|
ArgumentCaptor<ApplicationEvent> eventCaptor =
|
||||||
|
ArgumentCaptor.forClass(ApplicationEvent.class);
|
||||||
|
verify(appEventHandler).handle(eventCaptor.capture());
|
||||||
|
assertEquals(ApplicationEventType.APPLICATION_LOG_AGGREGATION_FINISHED,
|
||||||
|
eventCaptor.getValue().getType());
|
||||||
|
verify(appEventHandler).handle(eventCaptor.capture());
|
||||||
|
assertEquals(application1, eventCaptor.getValue()
|
||||||
|
.getApplicationID());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public void testMultipleAppsLogAggregation() throws IOException {
|
public void testMultipleAppsLogAggregation() throws IOException {
|
||||||
|
|
||||||
this.conf.set(YarnConfiguration.NM_LOG_DIRS, localLogDir.getAbsolutePath());
|
this.conf.set(YarnConfiguration.NM_LOG_DIRS, localLogDir.getAbsolutePath());
|
||||||
this.conf.set(YarnConfiguration.NM_REMOTE_APP_LOG_DIR,
|
this.conf.set(YarnConfiguration.NM_REMOTE_APP_LOG_DIR,
|
||||||
this.remoteRootLogDir.getAbsolutePath());
|
this.remoteRootLogDir.getAbsolutePath());
|
||||||
|
this.conf.setBoolean(YarnConfiguration.NM_LOG_AGGREGATION_ENABLED, true);
|
||||||
|
|
||||||
|
DrainDispatcher dispatcher = createDispatcher();
|
||||||
|
EventHandler<ApplicationEvent> appEventHandler = mock(EventHandler.class);
|
||||||
|
dispatcher.register(ApplicationEventType.class, appEventHandler);
|
||||||
|
|
||||||
LogAggregationService logAggregationService =
|
LogAggregationService logAggregationService =
|
||||||
new LogAggregationService(this.context, this.delSrvc);
|
new LogAggregationService(dispatcher, this.context, this.delSrvc);
|
||||||
logAggregationService.init(this.conf);
|
logAggregationService.init(this.conf);
|
||||||
logAggregationService.start();
|
logAggregationService.start();
|
||||||
|
|
||||||
|
@ -192,81 +251,69 @@ public class TestLogAggregationService extends BaseContainerManagerTest {
|
||||||
logAggregationService
|
logAggregationService
|
||||||
.handle(new LogAggregatorAppStartedEvent(
|
.handle(new LogAggregatorAppStartedEvent(
|
||||||
application1, this.user, null,
|
application1, this.user, null,
|
||||||
ContainerLogsRetentionPolicy.ALL_CONTAINERS));
|
ContainerLogsRetentionPolicy.ALL_CONTAINERS, this.acls));
|
||||||
|
|
||||||
ApplicationAttemptId appAttemptId1 =
|
ApplicationAttemptId appAttemptId1 =
|
||||||
recordFactory.newRecordInstance(ApplicationAttemptId.class);
|
BuilderUtils.newApplicationAttemptId(application1, 1);
|
||||||
appAttemptId1.setApplicationId(application1);
|
ContainerId container11 = BuilderUtils.newContainerId(appAttemptId1, 1);
|
||||||
ContainerId container11 =
|
|
||||||
BuilderUtils.newContainerId(recordFactory, application1, appAttemptId1, 1);
|
|
||||||
// Simulate log-file creation
|
// Simulate log-file creation
|
||||||
writeContainerLogs(app1LogDir, container11);
|
writeContainerLogs(app1LogDir, container11);
|
||||||
logAggregationService.handle(
|
logAggregationService.handle(
|
||||||
new LogAggregatorContainerFinishedEvent(container11, 0));
|
new LogAggregatorContainerFinishedEvent(container11, 0));
|
||||||
|
|
||||||
ApplicationId application2 = BuilderUtils.newApplicationId(1234, 2);
|
ApplicationId application2 = BuilderUtils.newApplicationId(1234, 2);
|
||||||
ApplicationAttemptId appAttemptId2 =
|
ApplicationAttemptId appAttemptId2 =
|
||||||
recordFactory.newRecordInstance(ApplicationAttemptId.class);
|
BuilderUtils.newApplicationAttemptId(application2, 1);
|
||||||
appAttemptId1.setApplicationId(application2);
|
|
||||||
|
|
||||||
File app2LogDir =
|
File app2LogDir =
|
||||||
new File(localLogDir, ConverterUtils.toString(application2));
|
new File(localLogDir, ConverterUtils.toString(application2));
|
||||||
app2LogDir.mkdir();
|
app2LogDir.mkdir();
|
||||||
logAggregationService.handle(new LogAggregatorAppStartedEvent(
|
logAggregationService.handle(new LogAggregatorAppStartedEvent(
|
||||||
application2, this.user, null,
|
application2, this.user, null,
|
||||||
ContainerLogsRetentionPolicy.APPLICATION_MASTER_ONLY));
|
ContainerLogsRetentionPolicy.APPLICATION_MASTER_ONLY, this.acls));
|
||||||
|
|
||||||
|
|
||||||
ContainerId container21 =
|
ContainerId container21 = BuilderUtils.newContainerId(appAttemptId2, 1);
|
||||||
BuilderUtils.newContainerId(recordFactory, application2,
|
|
||||||
appAttemptId2, 1);
|
|
||||||
writeContainerLogs(app2LogDir, container21);
|
writeContainerLogs(app2LogDir, container21);
|
||||||
logAggregationService.handle(
|
logAggregationService.handle(
|
||||||
new LogAggregatorContainerFinishedEvent(container21, 0));
|
new LogAggregatorContainerFinishedEvent(container21, 0));
|
||||||
|
|
||||||
ContainerId container12 =
|
ContainerId container12 = BuilderUtils.newContainerId(appAttemptId1, 2);
|
||||||
BuilderUtils.newContainerId(recordFactory, application1, appAttemptId1,
|
|
||||||
2);
|
|
||||||
writeContainerLogs(app1LogDir, container12);
|
writeContainerLogs(app1LogDir, container12);
|
||||||
logAggregationService.handle(
|
logAggregationService.handle(
|
||||||
new LogAggregatorContainerFinishedEvent(container12, 0));
|
new LogAggregatorContainerFinishedEvent(container12, 0));
|
||||||
|
|
||||||
ApplicationId application3 = BuilderUtils.newApplicationId(1234, 3);
|
ApplicationId application3 = BuilderUtils.newApplicationId(1234, 3);
|
||||||
ApplicationAttemptId appAttemptId3 =
|
ApplicationAttemptId appAttemptId3 =
|
||||||
recordFactory.newRecordInstance(ApplicationAttemptId.class);
|
BuilderUtils.newApplicationAttemptId(application3, 1);
|
||||||
appAttemptId1.setApplicationId(application3);
|
|
||||||
|
|
||||||
File app3LogDir =
|
File app3LogDir =
|
||||||
new File(localLogDir, ConverterUtils.toString(application3));
|
new File(localLogDir, ConverterUtils.toString(application3));
|
||||||
app3LogDir.mkdir();
|
app3LogDir.mkdir();
|
||||||
logAggregationService.handle(new LogAggregatorAppStartedEvent(
|
logAggregationService.handle(new LogAggregatorAppStartedEvent(application3,
|
||||||
application3, this.user, null,
|
this.user, null,
|
||||||
ContainerLogsRetentionPolicy.AM_AND_FAILED_CONTAINERS_ONLY));
|
ContainerLogsRetentionPolicy.AM_AND_FAILED_CONTAINERS_ONLY, this.acls));
|
||||||
|
|
||||||
|
|
||||||
ContainerId container31 =
|
ContainerId container31 = BuilderUtils.newContainerId(appAttemptId3, 1);
|
||||||
BuilderUtils.newContainerId(recordFactory, application3, appAttemptId3,
|
|
||||||
1);
|
|
||||||
writeContainerLogs(app3LogDir, container31);
|
writeContainerLogs(app3LogDir, container31);
|
||||||
logAggregationService.handle(
|
logAggregationService.handle(
|
||||||
new LogAggregatorContainerFinishedEvent(container31, 0));
|
new LogAggregatorContainerFinishedEvent(container31, 0));
|
||||||
|
|
||||||
ContainerId container32 =
|
ContainerId container32 = BuilderUtils.newContainerId(appAttemptId3, 2);
|
||||||
BuilderUtils.newContainerId(recordFactory, application3, appAttemptId3,
|
|
||||||
2);
|
|
||||||
writeContainerLogs(app3LogDir, container32);
|
writeContainerLogs(app3LogDir, container32);
|
||||||
logAggregationService.handle(
|
logAggregationService.handle(
|
||||||
new LogAggregatorContainerFinishedEvent(container32, 1)); // Failed
|
new LogAggregatorContainerFinishedEvent(container32, 1)); // Failed
|
||||||
|
|
||||||
ContainerId container22 =
|
ContainerId container22 = BuilderUtils.newContainerId(appAttemptId2, 2);
|
||||||
BuilderUtils.newContainerId(recordFactory, application2, appAttemptId2,
|
|
||||||
2);
|
|
||||||
writeContainerLogs(app2LogDir, container22);
|
writeContainerLogs(app2LogDir, container22);
|
||||||
logAggregationService.handle(
|
logAggregationService.handle(
|
||||||
new LogAggregatorContainerFinishedEvent(container22, 0));
|
new LogAggregatorContainerFinishedEvent(container22, 0));
|
||||||
|
|
||||||
ContainerId container33 =
|
ContainerId container33 = BuilderUtils.newContainerId(appAttemptId3, 3);
|
||||||
BuilderUtils.newContainerId(recordFactory, application3, appAttemptId3,
|
|
||||||
3);
|
|
||||||
writeContainerLogs(app3LogDir, container33);
|
writeContainerLogs(app3LogDir, container33);
|
||||||
logAggregationService.handle(
|
logAggregationService.handle(
|
||||||
new LogAggregatorContainerFinishedEvent(container33, 0));
|
new LogAggregatorContainerFinishedEvent(container33, 0));
|
||||||
|
@ -286,6 +333,22 @@ public class TestLogAggregationService extends BaseContainerManagerTest {
|
||||||
new ContainerId[] { container21 });
|
new ContainerId[] { container21 });
|
||||||
verifyContainerLogs(logAggregationService, application3,
|
verifyContainerLogs(logAggregationService, application3,
|
||||||
new ContainerId[] { container31, container32 });
|
new ContainerId[] { container31, container32 });
|
||||||
|
|
||||||
|
dispatcher.await();
|
||||||
|
ArgumentCaptor<ApplicationEvent> eventCaptor =
|
||||||
|
ArgumentCaptor.forClass(ApplicationEvent.class);
|
||||||
|
|
||||||
|
verify(appEventHandler, times(3)).handle(eventCaptor.capture());
|
||||||
|
List<ApplicationEvent> capturedEvents = eventCaptor.getAllValues();
|
||||||
|
Set<ApplicationId> appIds = new HashSet<ApplicationId>();
|
||||||
|
for (ApplicationEvent cap : capturedEvents) {
|
||||||
|
assertEquals(ApplicationEventType.APPLICATION_LOG_AGGREGATION_FINISHED,
|
||||||
|
eventCaptor.getValue().getType());
|
||||||
|
appIds.add(cap.getApplicationID());
|
||||||
|
}
|
||||||
|
assertTrue(appIds.contains(application1));
|
||||||
|
assertTrue(appIds.contains(application2));
|
||||||
|
assertTrue(appIds.contains(application3));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeContainerLogs(File appLogDir, ContainerId containerId)
|
private void writeContainerLogs(File appLogDir, ContainerId containerId)
|
||||||
|
@ -306,7 +369,11 @@ public class TestLogAggregationService extends BaseContainerManagerTest {
|
||||||
ContainerId[] expectedContainerIds) throws IOException {
|
ContainerId[] expectedContainerIds) throws IOException {
|
||||||
AggregatedLogFormat.LogReader reader =
|
AggregatedLogFormat.LogReader reader =
|
||||||
new AggregatedLogFormat.LogReader(this.conf,
|
new AggregatedLogFormat.LogReader(this.conf,
|
||||||
logAggregationService.getRemoteNodeLogFileForApp(appId));
|
logAggregationService.getRemoteNodeLogFileForApp(appId, this.user));
|
||||||
|
|
||||||
|
Assert.assertEquals(this.user, reader.getApplicationOwner());
|
||||||
|
verifyAcls(reader.getApplicationAcls());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Map<String, Map<String, String>> logMap =
|
Map<String, Map<String, String>> logMap =
|
||||||
new HashMap<String, Map<String, String>>();
|
new HashMap<String, Map<String, String>>();
|
||||||
|
@ -380,8 +447,10 @@ public class TestLogAggregationService extends BaseContainerManagerTest {
|
||||||
public void testLogAggregationForRealContainerLaunch() throws IOException,
|
public void testLogAggregationForRealContainerLaunch() throws IOException,
|
||||||
InterruptedException {
|
InterruptedException {
|
||||||
|
|
||||||
|
this.conf.setBoolean(YarnConfiguration.NM_LOG_AGGREGATION_ENABLED, true);
|
||||||
this.containerManager.start();
|
this.containerManager.start();
|
||||||
|
|
||||||
|
|
||||||
File scriptFile = new File(tmpDir, "scriptFile.sh");
|
File scriptFile = new File(tmpDir, "scriptFile.sh");
|
||||||
PrintWriter fileWriter = new PrintWriter(scriptFile);
|
PrintWriter fileWriter = new PrintWriter(scriptFile);
|
||||||
fileWriter.write("\necho Hello World! Stdout! > "
|
fileWriter.write("\necho Hello World! Stdout! > "
|
||||||
|
@ -400,13 +469,10 @@ public class TestLogAggregationService extends BaseContainerManagerTest {
|
||||||
recordFactory.newRecordInstance(ApplicationId.class);
|
recordFactory.newRecordInstance(ApplicationId.class);
|
||||||
appId.setClusterTimestamp(0);
|
appId.setClusterTimestamp(0);
|
||||||
appId.setId(0);
|
appId.setId(0);
|
||||||
ApplicationAttemptId appAttemptId =
|
ApplicationAttemptId appAttemptId =
|
||||||
recordFactory.newRecordInstance(ApplicationAttemptId.class);
|
BuilderUtils.newApplicationAttemptId(appId, 1);
|
||||||
appAttemptId.setApplicationId(appId);
|
ContainerId cId = BuilderUtils.newContainerId(appAttemptId, 0);
|
||||||
appAttemptId.setAttemptId(1);
|
|
||||||
ContainerId cId = recordFactory.newRecordInstance(ContainerId.class);
|
|
||||||
cId.setId(0);
|
|
||||||
cId.setApplicationAttemptId(appAttemptId);
|
|
||||||
containerLaunchContext.setContainerId(cId);
|
containerLaunchContext.setContainerId(cId);
|
||||||
|
|
||||||
containerLaunchContext.setUser(this.user);
|
containerLaunchContext.setUser(this.user);
|
||||||
|
@ -446,4 +512,27 @@ public class TestLogAggregationService extends BaseContainerManagerTest {
|
||||||
.asList(appId)));
|
.asList(appId)));
|
||||||
this.containerManager.stop();
|
this.containerManager.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void verifyAcls(Map<ApplicationAccessType, String> logAcls) {
|
||||||
|
Assert.assertEquals(this.acls.size(), logAcls.size());
|
||||||
|
for (ApplicationAccessType appAccessType : this.acls.keySet()) {
|
||||||
|
Assert.assertEquals(this.acls.get(appAccessType),
|
||||||
|
logAcls.get(appAccessType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DrainDispatcher createDispatcher() {
|
||||||
|
DrainDispatcher dispatcher = new DrainDispatcher();
|
||||||
|
dispatcher.init(this.conf);
|
||||||
|
dispatcher.start();
|
||||||
|
return dispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<ApplicationAccessType, String> createAppAcls() {
|
||||||
|
Map<ApplicationAccessType, String> appAcls =
|
||||||
|
new HashMap<ApplicationAccessType, String>();
|
||||||
|
appAcls.put(ApplicationAccessType.MODIFY_APP, "user group");
|
||||||
|
appAcls.put(ApplicationAccessType.VIEW_APP, "*");
|
||||||
|
return appAcls;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -194,8 +194,13 @@ public class AMLauncher implements Runnable {
|
||||||
// consumable by the AM.
|
// consumable by the AM.
|
||||||
environment.put(ApplicationConstants.AM_CONTAINER_ID_ENV, container
|
environment.put(ApplicationConstants.AM_CONTAINER_ID_ENV, container
|
||||||
.getContainerId().toString());
|
.getContainerId().toString());
|
||||||
environment.put(ApplicationConstants.NM_HTTP_ADDRESS_ENV, application
|
environment.put(ApplicationConstants.NM_HOST_ENV, application
|
||||||
.getMasterContainer().getNodeHttpAddress());
|
.getMasterContainer().getNodeId().getHost());
|
||||||
|
environment.put(ApplicationConstants.NM_PORT_ENV,
|
||||||
|
String.valueOf(application.getMasterContainer().getNodeId().getPort()));
|
||||||
|
String parts[] =
|
||||||
|
application.getMasterContainer().getNodeHttpAddress().split(":");
|
||||||
|
environment.put(ApplicationConstants.NM_HTTP_PORT_ENV, parts[1]);
|
||||||
environment.put(
|
environment.put(
|
||||||
ApplicationConstants.APP_SUBMIT_TIME_ENV,
|
ApplicationConstants.APP_SUBMIT_TIME_ENV,
|
||||||
String.valueOf(rmContext.getRMApps()
|
String.valueOf(rmContext.getRMApps()
|
||||||
|
|
|
@ -54,8 +54,8 @@ public class MockNM {
|
||||||
return nodeId;
|
return nodeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getHttpAddress() {
|
public int getHttpPort() {
|
||||||
return nodeId.getHost() + ":" + String.valueOf(httpPort);
|
return httpPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void containerStatus(Container container) throws Exception {
|
public void containerStatus(Container container) throws Exception {
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package org.apache.hadoop.yarn.server.resourcemanager;
|
package org.apache.hadoop.yarn.server.resourcemanager;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
@ -60,7 +61,9 @@ public class TestApplicationMasterLauncher {
|
||||||
boolean cleanedup = false;
|
boolean cleanedup = false;
|
||||||
String attemptIdAtContainerManager = null;
|
String attemptIdAtContainerManager = null;
|
||||||
String containerIdAtContainerManager = null;
|
String containerIdAtContainerManager = null;
|
||||||
String nmAddressAtContainerManager = null;
|
String nmHostAtContainerManager = null;
|
||||||
|
int nmPortAtContainerManager;
|
||||||
|
int nmHttpPortAtContainerManager;
|
||||||
long submitTimeAtContainerManager;
|
long submitTimeAtContainerManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -69,20 +72,22 @@ public class TestApplicationMasterLauncher {
|
||||||
throws YarnRemoteException {
|
throws YarnRemoteException {
|
||||||
LOG.info("Container started by MyContainerManager: " + request);
|
LOG.info("Container started by MyContainerManager: " + request);
|
||||||
launched = true;
|
launched = true;
|
||||||
|
Map<String, String> env =
|
||||||
|
request.getContainerLaunchContext().getEnvironment();
|
||||||
containerIdAtContainerManager =
|
containerIdAtContainerManager =
|
||||||
request.getContainerLaunchContext().getEnvironment()
|
env.get(ApplicationConstants.AM_CONTAINER_ID_ENV);
|
||||||
.get(ApplicationConstants.AM_CONTAINER_ID_ENV);
|
|
||||||
ContainerId containerId =
|
ContainerId containerId =
|
||||||
ConverterUtils.toContainerId(containerIdAtContainerManager);
|
ConverterUtils.toContainerId(containerIdAtContainerManager);
|
||||||
attemptIdAtContainerManager =
|
attemptIdAtContainerManager =
|
||||||
containerId.getApplicationAttemptId().toString();
|
containerId.getApplicationAttemptId().toString();
|
||||||
nmAddressAtContainerManager =
|
nmHostAtContainerManager = env.get(ApplicationConstants.NM_HOST_ENV);
|
||||||
request.getContainerLaunchContext().getEnvironment()
|
nmPortAtContainerManager =
|
||||||
.get(ApplicationConstants.NM_HTTP_ADDRESS_ENV);
|
Integer.parseInt(env.get(ApplicationConstants.NM_PORT_ENV));
|
||||||
|
nmHttpPortAtContainerManager =
|
||||||
|
Integer.parseInt(env.get(ApplicationConstants.NM_HTTP_PORT_ENV));
|
||||||
submitTimeAtContainerManager =
|
submitTimeAtContainerManager =
|
||||||
Long.parseLong(request.getContainerLaunchContext().getEnvironment()
|
Long.parseLong(env.get(ApplicationConstants.APP_SUBMIT_TIME_ENV));
|
||||||
.get(ApplicationConstants.APP_SUBMIT_TIME_ENV));
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,8 +173,12 @@ public class TestApplicationMasterLauncher {
|
||||||
Assert.assertEquals(app.getRMAppAttempt(appAttemptId)
|
Assert.assertEquals(app.getRMAppAttempt(appAttemptId)
|
||||||
.getSubmissionContext().getAMContainerSpec().getContainerId()
|
.getSubmissionContext().getAMContainerSpec().getContainerId()
|
||||||
.toString(), containerManager.containerIdAtContainerManager);
|
.toString(), containerManager.containerIdAtContainerManager);
|
||||||
Assert.assertEquals(nm1.getHttpAddress(),
|
Assert.assertEquals(nm1.getNodeId().getHost(),
|
||||||
containerManager.nmAddressAtContainerManager);
|
containerManager.nmHostAtContainerManager);
|
||||||
|
Assert.assertEquals(nm1.getNodeId().getPort(),
|
||||||
|
containerManager.nmPortAtContainerManager);
|
||||||
|
Assert.assertEquals(nm1.getHttpPort(),
|
||||||
|
containerManager.nmHttpPortAtContainerManager);
|
||||||
|
|
||||||
MockAM am = new MockAM(rm.getRMContext(), rm
|
MockAM am = new MockAM(rm.getRMContext(), rm
|
||||||
.getApplicationMasterService(), appAttemptId);
|
.getApplicationMasterService(), appAttemptId);
|
||||||
|
|
|
@ -325,9 +325,9 @@ Hadoop MapReduce Next Generation - Writing YARN Applications
|
||||||
|
|
||||||
* When the ApplicationMaster starts up, several parameters are made available
|
* When the ApplicationMaster starts up, several parameters are made available
|
||||||
to it via the environment. These include the ContainerId for the
|
to it via the environment. These include the ContainerId for the
|
||||||
ApplicationMaster container, the application submission time and the HTTP
|
ApplicationMaster container, the application submission time and details
|
||||||
address of the NodeManager running the container. Ref ApplicationConstants
|
about the NodeManager host running the Application Master.
|
||||||
for parameter names.
|
Ref ApplicationConstants for parameter names.
|
||||||
|
|
||||||
* All interactions with the ResourceManager require an ApplicationAttemptId
|
* All interactions with the ResourceManager require an ApplicationAttemptId
|
||||||
(there can be multiple attempts per application in case of failures). The
|
(there can be multiple attempts per application in case of failures). The
|
||||||
|
|
|
@ -265,6 +265,11 @@
|
||||||
<version>4.8.2</version>
|
<version>4.8.2</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>2.1</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mockito</groupId>
|
<groupId>org.mockito</groupId>
|
||||||
<artifactId>mockito-all</artifactId>
|
<artifactId>mockito-all</artifactId>
|
||||||
|
|
|
@ -156,6 +156,11 @@
|
||||||
<artifactId>netty</artifactId>
|
<artifactId>netty</artifactId>
|
||||||
<version>3.2.3.Final</version>
|
<version>3.2.3.Final</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>2.1</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.cenqua.clover</groupId>
|
<groupId>com.cenqua.clover</groupId>
|
||||||
<artifactId>clover</artifactId>
|
<artifactId>clover</artifactId>
|
||||||
|
|
Loading…
Reference in New Issue