YARN-3692. Allow REST API to set a user generated message when killing an application. Contributed by Rohith Sharma K S

This commit is contained in:
Naganarasimha 2016-09-24 21:13:01 +05:30
parent 6ca5ffe4b5
commit fa2025316d
11 changed files with 113 additions and 13 deletions

View File

@ -501,4 +501,10 @@ public void signalToContainer(ContainerId containerId,
throws YarnException, IOException {
client.signalToContainer(containerId, command);
}
@Override
public void killApplication(ApplicationId appId, String diagnostics)
throws YarnException, IOException {
client.killApplication(appId, diagnostics);
}
}

View File

@ -20,6 +20,7 @@
import org.apache.hadoop.classification.InterfaceAudience.Public;
import org.apache.hadoop.classification.InterfaceStability.Stable;
import org.apache.hadoop.classification.InterfaceStability.Unstable;
import org.apache.hadoop.yarn.api.ApplicationClientProtocol;
import org.apache.hadoop.yarn.api.records.ApplicationId;
import org.apache.hadoop.yarn.util.Records;
@ -57,4 +58,21 @@ public static KillApplicationRequest newInstance(ApplicationId applicationId) {
@Public
@Stable
public abstract void setApplicationId(ApplicationId applicationId);
/**
* Get the <em>diagnostics</em> to which the application is being killed.
* @return <em>diagnostics</em> to which the application is being killed
*/
@Public
@Unstable
public abstract String getDiagnostics();
/**
* Set the <em>diagnostics</em> to which the application is being killed.
* @param diagnostics <em>diagnostics</em> to which the application is being
* killed
*/
@Public
@Unstable
public abstract void setDiagnostics(String diagnostics);
}

View File

@ -153,6 +153,7 @@ message FailApplicationAttemptResponseProto {
message KillApplicationRequestProto {
optional ApplicationIdProto application_id = 1;
optional string diagnostics = 2;
}
message KillApplicationResponseProto {

View File

@ -172,6 +172,20 @@ public abstract void failApplicationAttempt(
public abstract void killApplication(ApplicationId applicationId) throws YarnException,
IOException;
/**
* <p>
* Kill an application identified by given ID.
* </p>
* @param applicationId {@link ApplicationId} of the application that needs to
* be killed
* @param diagnostics for killing an application.
* @throws YarnException in case of errors or if YARN rejects the request due
* to access-control restrictions.
* @throws IOException
*/
public abstract void killApplication(ApplicationId applicationId,
String diagnostics) throws YarnException, IOException;
/**
* <p>
* Get a report of the given Application.

View File

@ -400,10 +400,21 @@ public void failApplicationAttempt(ApplicationAttemptId attemptId)
@Override
public void killApplication(ApplicationId applicationId)
throws YarnException, IOException {
killApplication(applicationId, null);
}
@Override
public void killApplication(ApplicationId applicationId, String diagnostics)
throws YarnException, IOException {
KillApplicationRequest request =
Records.newRecord(KillApplicationRequest.class);
request.setApplicationId(applicationId);
if (diagnostics != null) {
request.setDiagnostics(diagnostics);
}
try {
int pollCount = 0;
long startTime = System.currentTimeMillis();
@ -417,14 +428,15 @@ public void killApplication(ApplicationId applicationId)
}
long elapsedMillis = System.currentTimeMillis() - startTime;
if (enforceAsyncAPITimeout() &&
elapsedMillis >= this.asyncApiPollTimeoutMillis) {
throw new YarnException("Timed out while waiting for application " +
applicationId + " to be killed.");
if (enforceAsyncAPITimeout()
&& elapsedMillis >= this.asyncApiPollTimeoutMillis) {
throw new YarnException("Timed out while waiting for application "
+ applicationId + " to be killed.");
}
if (++pollCount % 10 == 0) {
LOG.info("Waiting for application " + applicationId + " to be killed.");
LOG.info(
"Waiting for application " + applicationId + " to be killed.");
}
Thread.sleep(asyncApiPollIntervalMillis);
}

View File

@ -127,6 +127,24 @@ private ApplicationIdProto convertToProtoFormat(ApplicationId t) {
return ((ApplicationIdPBImpl)t).getProto();
}
@Override
public String getDiagnostics() {
KillApplicationRequestProtoOrBuilder p = viaProto ? proto : builder;
if (!p.hasDiagnostics()) {
return null;
}
return (p.getDiagnostics());
}
@Override
public void setDiagnostics(String diagnostics) {
maybeInitBuilder();
if (diagnostics == null) {
builder.clearDiagnostics();
return;
}
builder.setDiagnostics(diagnostics);
}
}

View File

@ -750,15 +750,25 @@ public KillApplicationResponse forceKillApplication(
return KillApplicationResponse.newInstance(true);
}
String message = "Kill application " + applicationId + " received from "
+ callerUGI;
StringBuilder message = new StringBuilder();
message.append("Application ").append(applicationId)
.append(" was killed by user ").append(callerUGI.getShortUserName());
InetAddress remoteAddress = Server.getRemoteIp();
if (null != remoteAddress) {
message += " at " + remoteAddress.getHostAddress();
message.append(" at ").append(remoteAddress.getHostAddress());
}
String diagnostics = org.apache.commons.lang.StringUtils
.trimToNull(request.getDiagnostics());
if (diagnostics != null) {
message.append(" with diagnostic message: ");
message.append(diagnostics);
}
this.rmContext.getDispatcher().getEventHandler()
.handle(new RMAppKillByClientEvent(applicationId, message, callerUGI,
remoteAddress));
.handle(new RMAppKillByClientEvent(applicationId, message.toString(),
callerUGI, remoteAddress));
// For UnmanagedAMs, return true so they don't retry
return KillApplicationResponse.newInstance(

View File

@ -789,7 +789,7 @@ public Response updateAppState(AppState targetState,
// allow users to kill the app
if (targetState.getState().equals(YarnApplicationState.KILLED.toString())) {
return killApp(app, callerUGI, hsr);
return killApp(app, callerUGI, hsr, targetState.getDiagnostics());
}
throw new BadRequestException("Only '"
+ YarnApplicationState.KILLED.toString()
@ -1006,7 +1006,8 @@ public NodeLabelsInfo getLabelsOnNode(@Context HttpServletRequest hsr,
}
protected Response killApp(RMApp app, UserGroupInformation callerUGI,
HttpServletRequest hsr) throws IOException, InterruptedException {
HttpServletRequest hsr, final String diagnostic)
throws IOException, InterruptedException {
if (app == null) {
throw new IllegalArgumentException("app cannot be null");
@ -1023,6 +1024,9 @@ public KillApplicationResponse run() throws IOException,
YarnException {
KillApplicationRequest req =
KillApplicationRequest.newInstance(appid);
if (diagnostic != null) {
req.setDiagnostics(diagnostic);
}
return rm.getClientRMService().forceKillApplication(req);
}
});

View File

@ -27,6 +27,7 @@
public class AppState {
String state;
private String diagnostics;
public AppState() {
}
@ -43,4 +44,11 @@ public String getState() {
return this.state;
}
public String getDiagnostics() {
return diagnostics;
}
public void setDiagnostics(String diagnostics) {
this.diagnostics = diagnostics;
}
}

View File

@ -501,7 +501,8 @@ public void testForceKillNonExistingApplication() throws YarnException {
@Test
public void testForceKillApplication() throws Exception {
YarnConfiguration conf = new YarnConfiguration();
MockRM rm = new MockRM();
conf.setBoolean(MockRM.ENABLE_WEBAPP, true);
MockRM rm = new MockRM(conf);
rm.init(conf);
rm.start();
@ -517,6 +518,8 @@ public void testForceKillApplication() throws Exception {
KillApplicationRequest killRequest1 =
KillApplicationRequest.newInstance(app1.getApplicationId());
String diagnostic = "message1";
killRequest1.setDiagnostics(diagnostic);
KillApplicationRequest killRequest2 =
KillApplicationRequest.newInstance(app2.getApplicationId());
@ -534,6 +537,8 @@ public void testForceKillApplication() throws Exception {
killAttemptCount > 1);
assertEquals("Incorrect number of apps in the RM", 1,
rmService.getApplications(getRequest).getApplicationList().size());
assertTrue("Diagnostic message is incorrect",
app1.getDiagnostics().toString().contains(diagnostic));
KillApplicationResponse killResponse2 =
rmService.forceKillApplication(killRequest2);

View File

@ -362,6 +362,7 @@ public void testSingleAppKill() throws Exception {
{ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML };
MediaType[] contentTypes =
{ MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE };
String diagnostic = "message1";
for (String mediaType : mediaTypes) {
for (MediaType contentType : contentTypes) {
RMApp app = rm.submitApp(CONTAINER_MB, "", webserviceUserName);
@ -369,6 +370,7 @@ public void testSingleAppKill() throws Exception {
AppState targetState =
new AppState(YarnApplicationState.KILLED.toString());
targetState.setDiagnostics(diagnostic);
Object entity;
if (contentType.equals(MediaType.APPLICATION_JSON_TYPE)) {
@ -423,6 +425,8 @@ public void testSingleAppKill() throws Exception {
} else {
verifyAppStateXML(response, RMAppState.KILLED);
}
assertTrue("Diagnostic message is incorrect",
app.getDiagnostics().toString().contains(diagnostic));
break;
}
}