AppsBlock.java
/**
* 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.router.webapp;
import static org.apache.commons.text.StringEscapeUtils.escapeHtml4;
import static org.apache.commons.text.StringEscapeUtils.escapeEcmaScript;
import static org.apache.hadoop.yarn.util.StringHelper.join;
import static org.apache.hadoop.yarn.webapp.YarnWebParams.APP_SC;
import static org.apache.hadoop.yarn.webapp.YarnWebParams.APP_STATE;
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.C_PROGRESSBAR;
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.C_PROGRESSBAR_VALUE;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.yarn.server.federation.store.records.SubClusterId;
import org.apache.hadoop.yarn.server.federation.store.records.SubClusterInfo;
import org.apache.hadoop.yarn.server.federation.utils.FederationStateStoreFacade;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.RMWSConsts;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.AppInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.AppsInfo;
import org.apache.hadoop.yarn.server.router.Router;
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 com.google.inject.Inject;
import javax.ws.rs.client.Client;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Applications block for the Router Web UI.
*/
public class AppsBlock extends RouterBlock {
private final Router router;
private final Configuration conf;
@Inject
AppsBlock(Router router, ViewContext ctx) {
super(router, ctx);
this.router = router;
this.conf = this.router.getConfig();
}
@Override
protected void render(Block html) {
boolean isEnabled = isYarnFederationEnabled();
// Get subClusterName
String subClusterName = $(APP_SC);
String reqState = $(APP_STATE);
// We will try to get the subClusterName.
// If the subClusterName is not empty,
// it means that we need to get the Node list of a subCluster.
AppsInfo appsInfo = null;
if (subClusterName != null && !subClusterName.isEmpty()) {
initSubClusterMetricsOverviewTable(html, subClusterName);
appsInfo = getSubClusterAppsInfo(subClusterName, reqState);
} else {
// Metrics Overview Table
html.__(MetricsOverviewTable.class);
appsInfo = getYarnFederationAppsInfo(isEnabled);
}
initYarnFederationAppsOfCluster(appsInfo, html);
}
private static String escape(String str) {
return escapeEcmaScript(escapeHtml4(str));
}
private AppsInfo getYarnFederationAppsInfo(boolean isEnabled) {
String webAddress = null;
if (isEnabled) {
webAddress = WebAppUtils.getRouterWebAppURLWithScheme(this.conf);
} else {
webAddress = WebAppUtils.getRMWebAppURLWithScheme(this.conf);
}
return getSubClusterAppsInfoByWebAddress(webAddress, StringUtils.EMPTY);
}
private AppsInfo getSubClusterAppsInfo(String subCluster, String states) {
try {
SubClusterId subClusterId = SubClusterId.newInstance(subCluster);
FederationStateStoreFacade facade = FederationStateStoreFacade.getInstance(this.conf);
SubClusterInfo subClusterInfo = facade.getSubCluster(subClusterId);
if (subClusterInfo != null) {
// Prepare webAddress
String webAddress = subClusterInfo.getRMWebServiceAddress();
String herfWebAppAddress;
if (webAddress != null && !webAddress.isEmpty()) {
herfWebAppAddress = WebAppUtils.getHttpSchemePrefix(conf) + webAddress;
return getSubClusterAppsInfoByWebAddress(herfWebAppAddress, states);
}
}
} catch (Exception e) {
LOG.error("get AppsInfo From SubCluster = {} error.", subCluster, e);
}
return null;
}
private AppsInfo getSubClusterAppsInfoByWebAddress(String webAddress, String states) {
Client client = RouterWebServiceUtil.createJerseyClient(conf);
Map<String, String[]> queryParams = new HashMap<>();
if (StringUtils.isNotBlank(states)) {
queryParams.put("states", new String[]{states});
}
AppsInfo apps = RouterWebServiceUtil
.genericForward(webAddress, null, AppsInfo.class, HTTPMethods.GET,
RMWSConsts.RM_WEB_SERVICE_PATH + RMWSConsts.APPS, null, queryParams, conf,
client);
client.close();
return apps;
}
private void initYarnFederationAppsOfCluster(AppsInfo appsInfo, Block html) {
TBODY<TABLE<Hamlet>> tbody = html.table("#apps").thead()
.tr()
.th(".id", "ID")
.th(".user", "User")
.th(".name", "Name")
.th(".type", "Application Type")
.th(".queue", "Queue")
.th(".priority", "Application Priority")
.th(".starttime", "StartTime")
.th(".finishtime", "FinishTime")
.th(".state", "State")
.th(".finalstatus", "FinalStatus")
.th(".progress", "Progress")
.th(".ui", "Tracking UI")
.__().__().tbody();
// Render the applications
StringBuilder appsTableData = new StringBuilder("[\n");
if (appsInfo != null && CollectionUtils.isNotEmpty(appsInfo.getApps())) {
List<String> appInfoList =
appsInfo.getApps().stream().map(this::parseAppInfoData).collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(appInfoList)) {
String formattedAppInfo = StringUtils.join(appInfoList, ",");
appsTableData.append(formattedAppInfo);
}
}
appsTableData.append("]");
html.script().$type("text/javascript")
.__("var appsTableData=" + appsTableData).__();
tbody.__().__();
}
private String parseAppInfoData(AppInfo app) {
StringBuilder appsDataBuilder = new StringBuilder();
try {
String percent = String.format("%.1f", app.getProgress() * 100.0F);
String trackingURL = app.getTrackingUrl() == null ? "#" : app.getTrackingUrl();
// AppID numerical value parsed by parseHadoopID in yarn.dt.plugins.js
appsDataBuilder.append("[\"")
.append("<a href='").append(trackingURL).append("'>")
.append(app.getAppId()).append("</a>\",\"")
.append(escape(app.getUser())).append("\",\"")
.append(escape(app.getName())).append("\",\"")
.append(escape(app.getApplicationType())).append("\",\"")
.append(escape(app.getQueue())).append("\",\"")
.append(app.getPriority()).append("\",\"")
.append(app.getStartTime()).append("\",\"")
.append(app.getFinishTime()).append("\",\"")
.append(app.getState()).append("\",\"")
.append(app.getFinalStatus()).append("\",\"")
// Progress bar
.append("<br title='").append(percent).append("'> <div class='")
.append(C_PROGRESSBAR).append("' title='")
.append(join(percent, '%')).append("'> ").append("<div class='")
.append(C_PROGRESSBAR_VALUE).append("' style='")
.append(join("width:", percent, '%')).append("'> </div> </div>")
// History link
.append("\",\"<a href='").append(trackingURL).append("'>")
.append("History").append("</a>");
appsDataBuilder.append("\"]\n");
} catch (Exception e) {
LOG.warn("Cannot add application {}: {}", app.getAppId(), e.getMessage());
}
return appsDataBuilder.toString();
}
}