blob: 3d9dbaffd6d9e2620d218db45ec029ddf30f4d90 [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.slider.server.appmaster.web.view;
import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Inject;
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet;
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.DIV;
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.LI;
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.UL;
import org.apache.slider.api.ClusterDescription;
import org.apache.slider.api.StatusKeys;
import org.apache.slider.api.types.ApplicationDiagnostics;
import org.apache.slider.api.types.ApplicationLivenessInformation;
import org.apache.slider.api.types.ContainerInformation;
import org.apache.slider.common.tools.SliderUtils;
import org.apache.slider.core.registry.docstore.ExportEntry;
import org.apache.slider.core.registry.docstore.PublishedExports;
import org.apache.slider.core.registry.docstore.PublishedExportsSet;
import org.apache.slider.providers.MonitorDetail;
import org.apache.slider.providers.ProviderService;
import org.apache.slider.server.appmaster.state.RoleStatus;
import org.apache.slider.server.appmaster.web.WebAppApi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import static org.apache.slider.server.appmaster.web.rest.RestPaths.LIVE_COMPONENTS;
/**
* The main content on the Slider AM web page
*/
public class IndexBlock extends SliderHamletBlock {
private static final Logger log = LoggerFactory.getLogger(IndexBlock.class);
/**
* Message printed when application is at full size.
*
* {@value}
*/
public static final String ALL_CONTAINERS_ALLOCATED = "all containers allocated";
@Inject
public IndexBlock(WebAppApi slider) {
super(slider);
}
@Override
protected void render(Block html) {
doIndex(html, getProviderName());
}
// An extra method to make testing easier since you can't make an instance of Block
@VisibleForTesting
protected void doIndex(Hamlet html, String providerName) {
ClusterDescription clusterStatus = appState.getClusterStatus();
String name = clusterStatus.name;
if (name != null && (name.startsWith(" ") || name.endsWith(" "))) {
name = "'" + name + "'";
}
DIV<Hamlet> div = html.div("general_info")
.h1("index_header",
"Application: " + name);
ApplicationLivenessInformation liveness =
appState.getApplicationLivenessInformation();
String livestatus = liveness.allRequestsSatisfied
? ALL_CONTAINERS_ALLOCATED
: String.format("Awaiting %d containers", liveness.requestsOutstanding);
Hamlet.TABLE<DIV<Hamlet>> table1 = div.table();
table1.tr()
.td("Status")
.td(livestatus)
._();
table1.tr()
.td("Total number of containers")
.td(Integer.toString(appState.getNumOwnedContainers()))
._();
table1.tr()
.td("Create time: ")
.td(getInfoAvoidingNulls(StatusKeys.INFO_CREATE_TIME_HUMAN))
._();
table1.tr()
.td("Running since: ")
.td(getInfoAvoidingNulls(StatusKeys.INFO_LIVE_TIME_HUMAN))
._();
table1.tr()
.td("Time last flexed: ")
.td(getInfoAvoidingNulls(StatusKeys.INFO_FLEX_TIME_HUMAN))
._();
table1.tr()
.td("Application storage path: ")
.td(clusterStatus.dataPath)
._();
table1.tr()
.td("Application configuration path: ")
.td(clusterStatus.originConfigurationPath)
._();
table1._();
div._();
div = null;
DIV<Hamlet> containers = html.div("container_instances")
.h3("Component Instances");
int aaRoleWithNoSuitableLocations = 0;
int aaRoleWithOpenRequest = 0;
int roleWithOpenRequest = 0;
Hamlet.TABLE<DIV<Hamlet>> table = containers.table();
Hamlet.TR<Hamlet.THEAD<Hamlet.TABLE<DIV<Hamlet>>>> header = table.thead().tr();
trb(header, "Component");
trb(header, "Desired");
trb(header, "Actual");
trb(header, "Outstanding Requests");
trb(header, "Failed");
trb(header, "Failed to start");
trb(header, "Placement");
header._()._(); // tr & thead
List<RoleStatus> roleStatuses = appState.cloneRoleStatusList();
Collections.sort(roleStatuses, new RoleStatus.CompareByName());
for (RoleStatus status : roleStatuses) {
String roleName = status.getName();
String nameUrl = apiPath(LIVE_COMPONENTS) + "/" + roleName;
String aatext;
if (status.isAntiAffinePlacement()) {
boolean aaRequestOutstanding = status.isAARequestOutstanding();
int pending = (int)status.getPendingAntiAffineRequests();
aatext = buildAADetails(aaRequestOutstanding, pending);
if (SliderUtils.isSet(status.getLabelExpression())) {
aatext += " (label: " + status.getLabelExpression() + ")";
}
if (pending > 0 && !aaRequestOutstanding) {
aaRoleWithNoSuitableLocations ++;
} else if (aaRequestOutstanding) {
aaRoleWithOpenRequest++;
}
} else {
if (SliderUtils.isSet(status.getLabelExpression())) {
aatext = "label: " + status.getLabelExpression();
} else {
aatext = "";
}
if (status.getRequested() > 0) {
roleWithOpenRequest ++;
}
}
table.tr()
.td().a(nameUrl, roleName)._()
.td(String.format("%d", status.getDesired()))
.td(String.format("%d", status.getActual()))
.td(String.format("%d", status.getRequested()))
.td(String.format("%d", status.getFailed()))
.td(String.format("%d", status.getStartFailed()))
.td(aatext)
._();
}
// empty row for some more spacing
table.tr()._();
// close table
table._();
containers._();
containers = null;
DIV<Hamlet> diagnostics = html.div("diagnostics");
List<String> statusEntries = new ArrayList<>(0);
if (roleWithOpenRequest > 0) {
statusEntries.add(String.format("%d %s with requests unsatisfiable by cluster",
roleWithOpenRequest, plural(roleWithOpenRequest, "component")));
}
if (aaRoleWithNoSuitableLocations > 0) {
statusEntries.add(String.format("%d anti-affinity %s no suitable nodes in the cluster",
aaRoleWithNoSuitableLocations,
plural(aaRoleWithNoSuitableLocations, "component has", "components have")));
}
if (aaRoleWithOpenRequest > 0) {
statusEntries.add(String.format("%d anti-affinity %s with requests unsatisfiable by cluster",
aaRoleWithOpenRequest,
plural(aaRoleWithOpenRequest, "component has", "components have")));
}
if (!statusEntries.isEmpty()) {
diagnostics.h3("Diagnostics");
Hamlet.TABLE<DIV<Hamlet>> diagnosticsTable = diagnostics.table();
for (String entry : statusEntries) {
diagnosticsTable.tr().td(entry)._();
}
diagnosticsTable._();
}
diagnostics._();
DIV<Hamlet> provider_info = html.div("provider_info");
provider_info.h3(providerName + " information");
UL<Hamlet> ul = html.ul();
addProviderServiceOptions(providerService, ul, clusterStatus);
ul._();
provider_info._();
DIV<Hamlet> exports = html.div("exports");
exports.h3("Exports");
ul = html.ul();
enumeratePublishedExports(appState.getPublishedExportsSet(), ul);
ul._();
exports._();
DIV<Hamlet> appDiagnosticsDiv = html.div("app_diagnostics")
.h3("Application Container Diagnostics");
Hamlet.TABLE<DIV<Hamlet>> appDiagnosticsTable = appDiagnosticsDiv.table();
Hamlet.TR<Hamlet.THEAD<Hamlet.TABLE<DIV<Hamlet>>>> appDiagnosticsHeader =
appDiagnosticsTable.thead().tr();
trb(appDiagnosticsHeader, "Container ID");
trb(appDiagnosticsHeader, "Component");
trb(appDiagnosticsHeader, "State");
trb(appDiagnosticsHeader, "Exit Code");
trb(appDiagnosticsHeader, "Logs");
trb(appDiagnosticsHeader, "Diagnostics");
appDiagnosticsHeader._()._(); // tr & thread
ApplicationDiagnostics appDiagnostics = appState
.getApplicationDiagnostics();
List<ContainerInformation> appContainers = new ArrayList<>(
appDiagnostics.getContainers());
Collections.sort(appContainers, new ContainerInformation.CompareById());
for (ContainerInformation appContainer : appContainers) {
String diagText = String.format("%s",
appContainer.getDiagnostics() == null ? ""
: appContainer.getDiagnostics());
appDiagnosticsTable.tr()
.td(appContainer.getContainerId())
.td(appContainer.getComponent())
.td(String.format("%d", appContainer.getState()))
.td(String.format("%d", appContainer.getExitCode()))
.td().a(appContainer.getLogLink(), "Logs")._()
.td(diagText)
._();
}
// close table and div
appDiagnosticsTable._();
appDiagnosticsDiv._();
appDiagnosticsDiv = null;
}
@VisibleForTesting
String buildAADetails(boolean outstanding, int pending) {
return String.format("Anti-affinity:%s %d pending %s",
(outstanding ? " 1 active request and" : ""),
pending, plural(pending, "request"));
}
private String plural(int n, String singular) {
return plural(n, singular, singular + "s");
}
private String plural(int n, String singular, String plural) {
return n == 1 ? singular : plural;
}
private void trb(Hamlet.TR tr,
String text) {
tr.td().b(text)._();
}
private String getProviderName() {
return providerService.getHumanName();
}
private String getInfoAvoidingNulls(String key) {
String createTime = appState.getClusterStatus().getInfo(key);
return null == createTime ? "N/A" : createTime;
}
protected void addProviderServiceOptions(ProviderService provider,
UL ul, ClusterDescription clusterStatus) {
Map<String, MonitorDetail> details = provider.buildMonitorDetails(
clusterStatus);
if (null == details) {
return;
}
// Loop over each entry, placing the text in the UL, adding an anchor when the URL is non-null/empty
for (Entry<String, MonitorDetail> entry : details.entrySet()) {
MonitorDetail detail = entry.getValue();
if (SliderUtils.isSet(detail.getValue()) ) {
LI item = ul.li();
item.span().$class("bold")._(entry.getKey())._();
item._(" - ");
if (detail.isUrl()) {
// Render an anchor if the value is a URL
item.a(detail.getValue(), detail.getValue())._();
} else {
item._(detail.getValue())._();
}
} else {
ul.li(entry.getKey());
}
}
}
protected void enumeratePublishedExports(PublishedExportsSet exports, UL<Hamlet> ul) {
for(String key : exports.keys()) {
PublishedExports export = exports.get(key);
LI<UL<Hamlet>> item = ul.li();
item.span().$class("bold")._(export.description)._();
UL sublist = item.ul();
for (Entry<String, List<ExportEntry>> entry : export.entries.entrySet()) {
LI sublistItem = sublist.li()._(entry.getKey());
for (ExportEntry exportEntry : entry.getValue()) {
sublistItem._(exportEntry.getValue());
}
sublistItem._();
}
sublist._();
item._();
}
}
}