| /** |
| * 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; |
| } |
| } |