blob: 9dd8aa1d2498aaad90bc1ad266769d656acd566c [file] [log] [blame]
/**
* 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.webapp;
import static org.apache.hadoop.yarn.util.StringHelper.join;
import static org.apache.hadoop.yarn.webapp.YarnWebParams.APPLICATION_ID;
import static org.apache.hadoop.yarn.webapp.YarnWebParams.WEB_UI_TYPE;
import java.io.IOException;
import java.security.PrivilegedExceptionAction;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.CommonConfigurationKeys;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.http.RestCsrfPreventionFilter;
import org.apache.hadoop.util.StringUtils;
import org.apache.hadoop.yarn.api.ApplicationBaseProtocol;
import org.apache.hadoop.yarn.api.protocolrecords.GetApplicationAttemptsRequest;
import org.apache.hadoop.yarn.api.protocolrecords.GetApplicationReportRequest;
import org.apache.hadoop.yarn.api.protocolrecords.GetContainerReportRequest;
import org.apache.hadoop.yarn.api.records.ApplicationAttemptReport;
import org.apache.hadoop.yarn.api.records.ApplicationId;
import org.apache.hadoop.yarn.api.records.ApplicationReport;
import org.apache.hadoop.yarn.api.records.ApplicationTimeoutType;
import org.apache.hadoop.yarn.api.records.ContainerReport;
import org.apache.hadoop.yarn.api.records.FinalApplicationStatus;
import org.apache.hadoop.yarn.api.records.LogAggregationStatus;
import org.apache.hadoop.yarn.api.records.YarnApplicationState;
import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.apache.hadoop.yarn.exceptions.ContainerNotFoundException;
import org.apache.hadoop.yarn.exceptions.YarnException;
import org.apache.hadoop.yarn.server.webapp.dao.AppAttemptInfo;
import org.apache.hadoop.yarn.server.webapp.dao.AppInfo;
import org.apache.hadoop.yarn.server.webapp.dao.ContainerInfo;
import org.apache.hadoop.yarn.util.Apps;
import org.apache.hadoop.yarn.util.Times;
import org.apache.hadoop.yarn.webapp.ResponseInfo;
import org.apache.hadoop.yarn.webapp.YarnWebParams;
import org.apache.hadoop.yarn.webapp.hamlet2.Hamlet;
import org.apache.hadoop.yarn.webapp.hamlet2.Hamlet.TABLE;
import org.apache.hadoop.yarn.webapp.hamlet2.Hamlet.TBODY;
import org.apache.hadoop.yarn.webapp.util.WebAppUtils;
import org.apache.hadoop.yarn.webapp.view.HtmlBlock;
import org.apache.hadoop.yarn.webapp.view.InfoBlock;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AppBlock extends HtmlBlock {
private static final Logger LOG = LoggerFactory.getLogger(AppBlock.class);
protected ApplicationBaseProtocol appBaseProt;
protected Configuration conf;
protected ApplicationId appID = null;
private boolean unsecuredUI = true;
@Inject
protected AppBlock(ApplicationBaseProtocol appBaseProt, ViewContext ctx,
Configuration conf) {
super(ctx);
this.appBaseProt = appBaseProt;
this.conf = conf;
// check if UI is unsecured.
String httpAuth = conf.get(CommonConfigurationKeys.HADOOP_HTTP_AUTHENTICATION_TYPE);
this.unsecuredUI = (httpAuth != null) && httpAuth.equals("simple");
}
@Override
protected void render(Block html) {
String webUiType = $(WEB_UI_TYPE);
String aid = $(APPLICATION_ID);
if (aid.isEmpty()) {
puts("Bad request: requires Application ID");
return;
}
try {
appID = Apps.toAppID(aid);
} catch (Exception e) {
puts("Invalid Application ID: " + aid);
return;
}
UserGroupInformation callerUGI = getCallerUGI();
ApplicationReport appReport;
try {
final GetApplicationReportRequest request =
GetApplicationReportRequest.newInstance(appID);
if (callerUGI == null) {
appReport = getApplicationReport(request);
} else {
appReport = callerUGI.doAs(
new PrivilegedExceptionAction<ApplicationReport> () {
@Override
public ApplicationReport run() throws Exception {
return getApplicationReport(request);
}
});
}
} catch (Exception e) {
String message = "Failed to read the application " + appID + ".";
LOG.error(message, e);
html.p().__(message).__();
return;
}
if (appReport == null) {
puts("Application not found: " + aid);
return;
}
AppInfo app = new AppInfo(appReport);
setTitle(join("Application ", aid));
//Validate if able to read application attempts
// which should also validate if kill is allowed for the user based on ACLs
Collection<ApplicationAttemptReport> attempts;
try {
final GetApplicationAttemptsRequest request =
GetApplicationAttemptsRequest.newInstance(appID);
if (callerUGI == null) {
attempts = getApplicationAttemptsReport(request);
} else {
attempts = callerUGI.doAs(
new PrivilegedExceptionAction<Collection<
ApplicationAttemptReport>>() {
@Override
public Collection<ApplicationAttemptReport> run()
throws Exception {
return getApplicationAttemptsReport(request);
}
});
}
} catch (Exception e) {
String message =
"Failed to read the attempts of the application " + appID + ".";
LOG.error(message, e);
html.p().__(message).__();
return;
}
// YARN-6890. for secured cluster allow anonymous UI access, application kill
// shouldn't be there.
boolean unsecuredUIForSecuredCluster = UserGroupInformation.isSecurityEnabled()
&& this.unsecuredUI;
if (webUiType != null
&& webUiType.equals(YarnWebParams.RM_WEB_UI)
&& conf.getBoolean(YarnConfiguration.RM_WEBAPP_UI_ACTIONS_ENABLED,
YarnConfiguration.DEFAULT_RM_WEBAPP_UI_ACTIONS_ENABLED)
&& !unsecuredUIForSecuredCluster
&& !isAppInFinalState(app)) {
// Application Kill
html.div()
.button()
.$onclick("confirmAction()").b("Kill Application").__()
.__();
StringBuilder script = new StringBuilder();
script.append("function confirmAction() {")
.append(" b = confirm(\"Are you sure?\");")
.append(" if (b == true) {")
.append(" $.ajax({")
.append(" type: 'PUT',")
.append(" url: '/ws/v1/cluster/apps/").append(aid).append("/state',")
.append(" contentType: 'application/json',")
.append(getCSRFHeaderString(conf))
.append(" data: '{\"state\":\"KILLED\"}',")
.append(" dataType: 'json'")
.append(" }).done(function(data){")
.append(" setTimeout(function(){")
.append(" location.href = '/cluster/app/").append(aid).append("';")
.append(" }, 1000);")
.append(" }).fail(function(data){")
.append(" console.log(data);")
.append(" });")
.append(" }")
.append("}");
html.script().$type("text/javascript").__(script.toString()).__();
}
String schedulerPath = WebAppUtils.getResolvedRMWebAppURLWithScheme(conf) +
"/cluster/scheduler?openQueues=" + app.getQueue();
generateOverviewTable(app, schedulerPath, webUiType, appReport);
createApplicationMetricsTable(html);
html.__(InfoBlock.class);
generateApplicationTable(html, callerUGI, attempts);
}
/**
* Generate overview table for app web page.
* @param app app info.
* @param schedulerPath schedule path.
* @param webUiType web ui type.
* @param appReport app report.
*/
private void generateOverviewTable(AppInfo app, String schedulerPath,
String webUiType, ApplicationReport appReport) {
ResponseInfo overviewTable = info("Application Overview")
.__("User:", schedulerPath, app.getUser())
.__("Name:", app.getName())
.__("Application Type:", app.getType())
.__("Application Tags:",
app.getApplicationTags() == null ? "" : app.getApplicationTags())
.__("Application Priority:", clarifyAppPriority(app.getPriority()))
.__(
"YarnApplicationState:",
app.getAppState() == null ? UNAVAILABLE : clarifyAppState(app
.getAppState()))
.__("Queue:", schedulerPath, app.getQueue())
.__("FinalStatus Reported by AM:",
clairfyAppFinalStatus(app.getFinalAppStatus()))
.__("Started:", Times.format(app.getStartedTime()))
.__("Launched:", Times.format(app.getLaunchTime()))
.__("Finished:", Times.format(app.getFinishedTime()))
.__("Elapsed:", StringUtils.formatTime(app.getElapsedTime()))
.__(
"Tracking URL:",
app.getTrackingUrl() == null
|| app.getTrackingUrl().equals(UNAVAILABLE) ? null : root_url(app
.getTrackingUrl()),
app.getTrackingUrl() == null
|| app.getTrackingUrl().equals(UNAVAILABLE) ? "Unassigned" : app
.getAppState() == YarnApplicationState.FINISHED
|| app.getAppState() == YarnApplicationState.FAILED
|| app.getAppState() == YarnApplicationState.KILLED ? "History"
: "ApplicationMaster");
if (webUiType != null
&& webUiType.equals(YarnWebParams.RM_WEB_UI)) {
LogAggregationStatus status = getLogAggregationStatus();
if (status == null) {
overviewTable.__("Log Aggregation Status:", "N/A");
} else if (status == LogAggregationStatus.DISABLED
|| status == LogAggregationStatus.NOT_START
|| status == LogAggregationStatus.SUCCEEDED) {
overviewTable.__("Log Aggregation Status:", status.name());
} else {
overviewTable.__("Log Aggregation Status:",
root_url("logaggregationstatus", app.getAppId()), status.name());
}
long timeout = appReport.getApplicationTimeouts()
.get(ApplicationTimeoutType.LIFETIME).getRemainingTime();
if (timeout < 0) {
overviewTable.__("Application Timeout (Remaining Time):", "Unlimited");
} else {
overviewTable.__("Application Timeout (Remaining Time):",
String.format("%d seconds", timeout));
}
}
overviewTable.__("Diagnostics:",
app.getDiagnosticsInfo() == null ? "" : app.getDiagnosticsInfo());
overviewTable.__("Unmanaged Application:", app.isUnmanagedApp());
overviewTable.__("Application Node Label expression:",
app.getAppNodeLabelExpression() == null ? "<Not set>"
: app.getAppNodeLabelExpression());
overviewTable.__("AM container Node Label expression:",
app.getAmNodeLabelExpression() == null ? "<Not set>"
: app.getAmNodeLabelExpression());
}
protected void generateApplicationTable(Block html,
UserGroupInformation callerUGI,
Collection<ApplicationAttemptReport> attempts) {
// Application Attempt Table
TBODY<TABLE<Hamlet>> tbody =
html.table("#attempts").thead().tr().th(".id", "Attempt ID")
.th(".started", "Started").th(".node", "Node").th(".logs", "Logs")
.__().__().tbody();
StringBuilder attemptsTableData = new StringBuilder("[\n");
for (final ApplicationAttemptReport appAttemptReport : attempts) {
AppAttemptInfo appAttempt = new AppAttemptInfo(appAttemptReport);
ContainerReport containerReport;
try {
final GetContainerReportRequest request =
GetContainerReportRequest.newInstance(
appAttemptReport.getAMContainerId());
if (callerUGI == null) {
containerReport =
getContainerReport(request);
} else {
containerReport = callerUGI.doAs(
new PrivilegedExceptionAction<ContainerReport>() {
@Override
public ContainerReport run() throws Exception {
ContainerReport report = null;
if (request.getContainerId() != null) {
try {
report = getContainerReport(request);
} catch (ContainerNotFoundException ex) {
LOG.warn(ex.getMessage());
}
}
return report;
}
});
}
} catch (Exception e) {
String message =
"Failed to read the AM container of the application attempt "
+ appAttemptReport.getApplicationAttemptId() + ".";
LOG.error(message, e);
html.p().__(message).__();
return;
}
long startTime = 0L;
String logsLink = null;
String nodeLink = null;
if (containerReport != null) {
ContainerInfo container = new ContainerInfo(containerReport);
startTime = container.getStartedTime();
logsLink = containerReport.getLogUrl();
nodeLink = containerReport.getNodeHttpAddress();
}
attemptsTableData
.append("[\"<a href='")
.append(url("appattempt", appAttempt.getAppAttemptId()))
.append("'>")
.append(appAttempt.getAppAttemptId())
.append("</a>\",\"")
.append(startTime)
.append("\",\"<a ")
.append(nodeLink == null ? "#" : "href='" + nodeLink)
.append("'>")
.append(nodeLink == null ? "N/A" : StringEscapeUtils
.escapeJavaScript(StringEscapeUtils.escapeHtml(nodeLink)))
.append("</a>\",\"<a ")
.append(logsLink == null ? "#" : "href='" + logsLink).append("'>")
.append(logsLink == null ? "N/A" : "Logs").append("</a>\"],\n");
}
if (attemptsTableData.charAt(attemptsTableData.length() - 2) == ',') {
attemptsTableData.delete(attemptsTableData.length() - 2,
attemptsTableData.length() - 1);
}
attemptsTableData.append("]");
html.script().$type("text/javascript")
.__("var attemptsTableData=" + attemptsTableData).__();
tbody.__().__();
}
protected ContainerReport getContainerReport(
final GetContainerReportRequest request)
throws YarnException, IOException {
return appBaseProt.getContainerReport(request).getContainerReport();
}
protected List<ApplicationAttemptReport> getApplicationAttemptsReport(
final GetApplicationAttemptsRequest request)
throws YarnException, IOException {
return appBaseProt.getApplicationAttempts(request)
.getApplicationAttemptList();
}
protected ApplicationReport getApplicationReport(
final GetApplicationReportRequest request)
throws YarnException, IOException {
return appBaseProt.getApplicationReport(request).getApplicationReport();
}
private String clarifyAppState(YarnApplicationState state) {
String ret = state.toString();
switch (state) {
case NEW:
return ret + ": waiting for application to be initialized";
case NEW_SAVING:
return ret + ": waiting for application to be persisted in state-store.";
case SUBMITTED:
return ret + ": waiting for application to be accepted by scheduler.";
case ACCEPTED:
return ret + ": waiting for AM container to be allocated, launched and"
+ " register with RM.";
case RUNNING:
return ret + ": AM has registered with RM and started running.";
default:
return ret;
}
}
private String clarifyAppPriority(int priority) {
return priority + " (Higher Integer value indicates higher priority)";
}
private String clairfyAppFinalStatus(FinalApplicationStatus status) {
if (status == FinalApplicationStatus.UNDEFINED) {
return "Application has not completed yet.";
}
return status.toString();
}
// The preemption metrics only need to be shown in RM WebUI
protected void createApplicationMetricsTable(Block html) {
}
// This will be overrided in RMAppBlock
protected LogAggregationStatus getLogAggregationStatus() {
return null;
}
public static String getCSRFHeaderString(Configuration conf) {
String ret = "";
if (conf.getBoolean(YarnConfiguration.RM_CSRF_ENABLED, false)) {
ret = " headers : { '";
Map<String, String> filterParams = RestCsrfPreventionFilter
.getFilterParams(conf, YarnConfiguration.RM_CSRF_PREFIX);
if (filterParams
.containsKey(RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)) {
ret += filterParams.get(RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM);
} else {
ret += RestCsrfPreventionFilter.HEADER_DEFAULT;
}
ret += "' : 'null' },";
}
return ret;
}
private boolean isAppInFinalState(AppInfo app) {
return app.getAppState() == YarnApplicationState.FINISHED
|| app.getAppState() == YarnApplicationState.FAILED
|| app.getAppState() == YarnApplicationState.KILLED;
}
}