YARN-11345. [Federation] Refactoring Yarn Router's Application Web Page. (#5030)
This commit is contained in:
parent
833750f72a
commit
454157a384
@ -35,6 +35,7 @@ public interface YarnWebParams {
|
||||
String APP_STATE = "app.state";
|
||||
String APP_START_TIME_BEGIN = "app.started-time.begin";
|
||||
String APP_START_TIME_END = "app.started-time.end";
|
||||
String APP_SC = "app.subcluster";
|
||||
String APPS_NUM = "apps.num";
|
||||
String QUEUE_NAME = "queue.name";
|
||||
String NODE_STATE = "node.state";
|
||||
|
@ -21,11 +21,18 @@
|
||||
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 com.sun.jersey.api.client.Client;
|
||||
import org.apache.commons.collections.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;
|
||||
@ -34,34 +41,102 @@
|
||||
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 com.google.inject.Inject;
|
||||
|
||||
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 HtmlBlock {
|
||||
public class AppsBlock extends RouterBlock {
|
||||
|
||||
private final Router router;
|
||||
private final Configuration conf;
|
||||
|
||||
@Inject
|
||||
AppsBlock(Router router, ViewContext ctx) {
|
||||
super(ctx);
|
||||
super(router, ctx);
|
||||
this.router = router;
|
||||
this.conf = this.router.getConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(Block html) {
|
||||
// Get the applications from the Resource Managers
|
||||
Configuration conf = this.router.getConfig();
|
||||
Client client = RouterWebServiceUtil.createJerseyClient(conf);
|
||||
String webAppAddress = WebAppUtils.getRouterWebAppURLWithScheme(conf);
|
||||
AppsInfo apps = RouterWebServiceUtil
|
||||
.genericForward(webAppAddress, null, AppsInfo.class, HTTPMethods.GET,
|
||||
RMWSConsts.RM_WEB_SERVICE_PATH + RMWSConsts.APPS, null, null, conf,
|
||||
client);
|
||||
|
||||
setTitle("Applications");
|
||||
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) {
|
||||
if (isEnabled) {
|
||||
String webAddress = WebAppUtils.getRouterWebAppURLWithScheme(this.conf);
|
||||
return getSubClusterAppsInfoByWebAddress(webAddress, StringUtils.EMPTY);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private AppsInfo getSubClusterAppsInfo(String subCluster, String states) {
|
||||
try {
|
||||
SubClusterId subClusterId = SubClusterId.newInstance(subCluster);
|
||||
FederationStateStoreFacade facade = FederationStateStoreFacade.getInstance();
|
||||
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.destroy();
|
||||
return apps;
|
||||
}
|
||||
|
||||
private void initYarnFederationAppsOfCluster(AppsInfo appsInfo, Block html) {
|
||||
|
||||
TBODY<TABLE<Hamlet>> tbody = html.table("#apps").thead()
|
||||
.tr()
|
||||
@ -81,45 +156,18 @@ protected void render(Block html) {
|
||||
|
||||
// Render the applications
|
||||
StringBuilder appsTableData = new StringBuilder("[\n");
|
||||
for (AppInfo app : apps.getApps()) {
|
||||
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
|
||||
appsTableData.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(String.valueOf(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>");
|
||||
appsTableData.append("\"],\n");
|
||||
if (appsInfo != null && CollectionUtils.isNotEmpty(appsInfo.getApps())) {
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.info(
|
||||
"Cannot add application {}: {}", app.getAppId(), e.getMessage());
|
||||
List<String> appInfoList =
|
||||
appsInfo.getApps().stream().map(this::parseAppInfoData).collect(Collectors.toList());
|
||||
|
||||
if (CollectionUtils.isNotEmpty(appInfoList)) {
|
||||
String formattedAppInfo = StringUtils.join(appInfoList, ",");
|
||||
appsTableData.append(formattedAppInfo);
|
||||
}
|
||||
}
|
||||
if (appsTableData.charAt(appsTableData.length() - 2) == ',') {
|
||||
appsTableData.delete(appsTableData.length() - 2,
|
||||
appsTableData.length() - 1);
|
||||
}
|
||||
|
||||
appsTableData.append("]");
|
||||
html.script().$type("text/javascript")
|
||||
.__("var appsTableData=" + appsTableData).__();
|
||||
@ -127,7 +175,39 @@ protected void render(Block html) {
|
||||
tbody.__().__();
|
||||
}
|
||||
|
||||
private static String escape(String str) {
|
||||
return escapeEcmaScript(escapeHtml4(str));
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -20,11 +20,13 @@
|
||||
|
||||
import static org.apache.hadoop.yarn.util.StringHelper.sjoin;
|
||||
import static org.apache.hadoop.yarn.webapp.YarnWebParams.APP_STATE;
|
||||
import static org.apache.hadoop.yarn.webapp.YarnWebParams.APP_SC;
|
||||
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.DATATABLES;
|
||||
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.DATATABLES_ID;
|
||||
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.initID;
|
||||
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.tableInit;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hadoop.yarn.webapp.SubView;
|
||||
|
||||
class AppsPage extends RouterView {
|
||||
@ -37,9 +39,14 @@ protected void preHead(Page.HTML<__> html) {
|
||||
setTableStyles(html, "apps", ".queue {width:6em}", ".ui {width:8em}");
|
||||
|
||||
// Set the correct title.
|
||||
String subClusterName = $(APP_SC);
|
||||
String reqState = $(APP_STATE);
|
||||
reqState = (reqState == null || reqState.isEmpty() ? "All" : reqState);
|
||||
setTitle(sjoin(reqState, "Applications"));
|
||||
|
||||
if(StringUtils.isBlank(subClusterName)){
|
||||
subClusterName = "Federation ";
|
||||
}
|
||||
reqState = (StringUtils.isBlank(reqState) ? "All" : reqState);
|
||||
setTitle(sjoin(subClusterName, reqState, "Applications"));
|
||||
}
|
||||
|
||||
private String appsTableInit() {
|
||||
|
@ -20,12 +20,12 @@
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import org.apache.hadoop.conf.Configuration;
|
||||
import org.apache.hadoop.yarn.api.records.YarnApplicationState;
|
||||
import org.apache.hadoop.yarn.server.router.Router;
|
||||
import org.apache.hadoop.yarn.server.webapp.WebPageUtils;
|
||||
import org.apache.hadoop.yarn.webapp.hamlet2.Hamlet;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Navigation block for the Router Web UI.
|
||||
*/
|
||||
@ -60,7 +60,24 @@ public void render(Block html) {
|
||||
subAppsList1.__().__();
|
||||
|
||||
// ### applications info
|
||||
mainList.li().a(url("apps"), "Applications").__();
|
||||
Hamlet.UL<Hamlet.LI<Hamlet.UL<Hamlet.DIV<Hamlet>>>> subAppsList2 =
|
||||
mainList.li().a(url("apps"), "Applications").ul();
|
||||
|
||||
subAppsList2.li().__();
|
||||
for (String subClusterId : subClusterIds) {
|
||||
Hamlet.LI<Hamlet.UL<Hamlet.LI<Hamlet.UL<Hamlet.DIV<Hamlet>>>>> subAppsList3 = subAppsList2.
|
||||
li().a(url("apps", subClusterId), subClusterId);
|
||||
Hamlet.UL<Hamlet.LI<Hamlet.UL<Hamlet.LI<Hamlet.UL<Hamlet.DIV<Hamlet>>>>>> subAppsList4 =
|
||||
subAppsList3.ul().$style("padding:0.3em 1em 0.1em 2em");
|
||||
subAppsList4.li().__();
|
||||
for (YarnApplicationState state : YarnApplicationState.values()) {
|
||||
subAppsList4.
|
||||
li().a(url("apps", subClusterId, state.toString()), state.toString()).__();
|
||||
}
|
||||
subAppsList4.li().__().__();
|
||||
subAppsList3.__();
|
||||
}
|
||||
subAppsList2.__().__();
|
||||
|
||||
// ### tools
|
||||
Hamlet.DIV<Hamlet> sectionBefore = mainList.__();
|
||||
|
@ -116,6 +116,7 @@ private NodesInfo getSubClusterNodesInfoByWebAddress(String webAddress) {
|
||||
.genericForward(webAddress, null, NodesInfo.class, HTTPMethods.GET,
|
||||
RMWSConsts.RM_WEB_SERVICE_PATH + RMWSConsts.NODES, null, null, conf,
|
||||
client);
|
||||
client.destroy();
|
||||
return nodes;
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ public void setup() {
|
||||
route("/", RouterController.class);
|
||||
route("/cluster", RouterController.class, "about");
|
||||
route("/about", RouterController.class, "about");
|
||||
route("/apps", RouterController.class, "apps");
|
||||
route(pajoin("/apps", APP_SC, APP_STATE), RouterController.class, "apps");
|
||||
route(pajoin("/nodes", NODE_SC), RouterController.class, "nodes");
|
||||
route("/federation", RouterController.class, "federation");
|
||||
}
|
||||
|
@ -81,4 +81,22 @@ public void testFederationNodeViewNotEnable()
|
||||
config.setBoolean(YarnConfiguration.FEDERATION_ENABLED, false);
|
||||
WebAppTests.testPage(NodesPage.class, Router.class, new MockRouter(config));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFederationAppViewEnable()
|
||||
throws InterruptedException, YarnException, IOException {
|
||||
// Test Federation Enabled
|
||||
Configuration config = new YarnConfiguration();
|
||||
config.setBoolean(YarnConfiguration.FEDERATION_ENABLED, true);
|
||||
WebAppTests.testPage(AppsPage.class, Router.class, new MockRouter(config));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFederationAppViewNotEnable()
|
||||
throws InterruptedException, YarnException, IOException {
|
||||
// Test Federation Not Enabled
|
||||
Configuration config = new YarnConfiguration();
|
||||
config.setBoolean(YarnConfiguration.FEDERATION_ENABLED, false);
|
||||
WebAppTests.testPage(AppsPage.class, Router.class, new MockRouter(config));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user