YARN-4515. [YARN-3368] Support hosting web UI framework inside YARN RM. (Sunil G via wangda)

YARN-5000. [YARN-3368] App attempt page is not loading when timeline server is not started (Sunil G via wangda)
YARN-5038. [YARN-3368] Application and Container pages shows wrong values when RM is stopped. (Sunil G via wangda)
This commit is contained in:
Wangda Tan 2016-05-17 22:28:24 -07:00
parent 2c4f164e16
commit c85cc3b56e
47 changed files with 486 additions and 93 deletions

View File

@ -2328,6 +2328,7 @@ The Apache Hadoop YARN Web UI component bundles the following files under the MI
- datatables v1.10.8 (https://datatables.net/)
- moment v2.10.6 (http://momentjs.com/) - Copyright (c) 2011-2015 Tim Wood, Iskren Chernev, Moment.js contributors
- em-helpers v0.5.8 (https://github.com/sreenaths/em-helpers)
- ember-array-contains-helper v1.0.2 (https://github.com/bmeurant/ember-array-contains-helper)
- ember-cli-app-version v0.5.8 (https://github.com/EmberSherpa/ember-cli-app-version) - Authored by Taras Mankovski <tarasm@gmail.com>
- ember-cli-babel v5.1.6 (https://github.com/babel/ember-cli-babel) - Authored by Stefan Penner <stefan.penner@gmail.com>
- ember-cli-content-security-policy v0.4.0 (https://github.com/rwjblue/ember-cli-content-security-policy)
@ -2341,6 +2342,7 @@ The Apache Hadoop YARN Web UI component bundles the following files under the MI
- ember-cli-sri v1.2.1 (https://github.com/jonathanKingston/ember-cli-sri) - Authored by Jonathan Kingston
- ember-cli-uglify v1.2.0 (github.com/ember-cli/ember-cli-uglify) - Authored by Robert Jackson <me@rwjblue.com>
- ember-d3 v0.1.0 (https://github.com/brzpegasus/ember-d3) - Authored by Estelle DeBlois
- ember-truth-helpers v1.2.0 (https://github.com/jmurphyau/ember-truth-helpers)
- select2 v4.0.0 (https://select2.github.io/)
All rights reserved.

View File

@ -192,6 +192,13 @@
<directory>${project.build.directory}/site</directory>
<outputDirectory>/share/doc/hadoop/${hadoop.component}</outputDirectory>
</fileSet>
<fileSet>
<directory>hadoop-yarn/hadoop-yarn-ui/target/hadoop-yarn-ui-${project.version}</directory>
<outputDirectory>/share/hadoop/${hadoop.component}/webapps/rm</outputDirectory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
</fileSets>
<moduleSets>
<moduleSet>

View File

@ -262,7 +262,30 @@ public class YarnConfiguration extends Configuration {
public static final int DEFAULT_RM_WEBAPP_HTTPS_PORT = 8090;
public static final String DEFAULT_RM_WEBAPP_HTTPS_ADDRESS = "0.0.0.0:"
+ DEFAULT_RM_WEBAPP_HTTPS_PORT;
/**
* Enable YARN WebApp V2.
*/
public static final String RM_WEBAPP_UI2_ENABLE = RM_PREFIX
+ "webapp.ui2.enable";
public static final boolean DEFAULT_RM_WEBAPP_UI2_ENABLE = false;
/** The address of the RM web ui2 application. */
public static final String RM_WEBAPP_UI2_ADDRESS = RM_PREFIX
+ "webapp.ui2.address";
public static final int DEFAULT_RM_WEBAPP_UI2_PORT = 8288;
public static final String DEFAULT_RM_WEBAPP_UI2_ADDRESS = "0.0.0.0:" +
DEFAULT_RM_WEBAPP_UI2_PORT;
/** The https address of the RM web ui2 application.*/
public static final String RM_WEBAPP_UI2_HTTPS_ADDRESS =
RM_PREFIX + "webapp.ui2.https.address";
public static final int DEFAULT_RM_WEBAPP_UI2_HTTPS_PORT = 8290;
public static final String DEFAULT_RM_WEBAPP_UI2_HTTPS_ADDRESS = "0.0.0.0:"
+ DEFAULT_RM_WEBAPP_UI2_HTTPS_PORT;
public static final String RM_RESOURCE_TRACKER_ADDRESS =
RM_PREFIX + "resource-tracker.address";
public static final int DEFAULT_RM_RESOURCE_TRACKER_PORT = 8031;

View File

@ -179,6 +179,32 @@
<value>true</value>
</property>
<property>
<description>To enable RM web ui2 application.</description>
<name>yarn.resourcemanager.webapp.ui2.enable</name>
<value>false</value>
</property>
<property>
<description>
The http address of the RM web ui2 application.
If only a host is provided as the value,
the webapp will be served on a random port.
</description>
<name>yarn.resourcemanager.webapp.ui2.address</name>
<value>${yarn.resourcemanager.hostname}:8288</value>
</property>
<property>
<description>
The https address of the RM web ui2 application.
If only a host is provided as the value,
the webapp will be served on a random port.
</description>
<name>yarn.resourcemanager.webapp.ui2.https.address</name>
<value>${yarn.resourcemanager.hostname}:8290</value>
</property>
<property>
<name>yarn.resourcemanager.resource-tracker.address</name>
<value>${yarn.resourcemanager.hostname}:8031</value>

View File

@ -18,16 +18,6 @@
package org.apache.hadoop.yarn.server.resourcemanager;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.security.PrivilegedExceptionAction;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.curator.framework.AuthInfo;
@ -38,10 +28,12 @@ import org.apache.hadoop.classification.InterfaceAudience.Private;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.ha.HAServiceProtocol;
import org.apache.hadoop.ha.HAServiceProtocol.HAServiceState;
import org.apache.hadoop.http.HttpServer2;
import org.apache.hadoop.http.lib.StaticUserWebFilter;
import org.apache.hadoop.metrics2.MetricsSystem;
import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
import org.apache.hadoop.metrics2.source.JvmMetrics;
import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.security.AuthenticationFilterInitializer;
import org.apache.hadoop.security.Groups;
import org.apache.hadoop.security.HttpCrossOriginFilterInitializer;
@ -124,6 +116,16 @@ import org.apache.hadoop.yarn.webapp.util.WebAppUtils;
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.Charset;
import java.security.PrivilegedExceptionAction;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
/**
* The ResourceManager is the main class that is a set of components.
@ -909,7 +911,49 @@ public class ResourceManager extends CompositeService implements Recoverable {
}
}
}
/**
* Return a HttpServer.Builder that the journalnode / namenode / secondary
* namenode can use to initialize their HTTP / HTTPS server.
*
*/
public static HttpServer2.Builder httpServerTemplateForRM(Configuration conf,
final InetSocketAddress httpAddr, final InetSocketAddress httpsAddr,
String name) throws IOException {
HttpServer2.Builder builder = new HttpServer2.Builder().setName(name)
.setConf(conf).setSecurityEnabled(false);
if (httpAddr.getPort() == 0) {
builder.setFindPort(true);
}
URI uri = URI.create("http://" + NetUtils.getHostPortString(httpAddr));
builder.addEndpoint(uri);
LOG.info("Starting Web-server for " + name + " at: " + uri);
return builder;
}
protected void startWebAppV2() throws IOException {
Configuration config = getConfig();
final InetSocketAddress httpAddr = config.getSocketAddr(
YarnConfiguration.RM_WEBAPP_UI2_ADDRESS,
YarnConfiguration.DEFAULT_RM_WEBAPP_UI2_ADDRESS,
YarnConfiguration.DEFAULT_RM_WEBAPP_UI2_PORT);
final InetSocketAddress httpsAddr = config.getSocketAddr(
YarnConfiguration.RM_WEBAPP_UI2_HTTPS_ADDRESS,
YarnConfiguration.DEFAULT_RM_WEBAPP_UI2_HTTPS_ADDRESS,
YarnConfiguration.DEFAULT_RM_WEBAPP_UI2_HTTPS_PORT);
HttpServer2.Builder builder = httpServerTemplateForRM(config, httpAddr,
httpsAddr, "rm");
HttpServer2 infoServer = builder.build();
infoServer.start();
LOG.info("Web server init done");
}
protected void startWepApp() {
// Use the customized yarn filter instead of the standard kerberos filter to
@ -1128,6 +1172,16 @@ public class ResourceManager extends CompositeService implements Recoverable {
transitionToActive();
}
if (getConfig().getBoolean(YarnConfiguration.RM_WEBAPP_UI2_ENABLE,
YarnConfiguration.DEFAULT_RM_WEBAPP_UI2_ENABLE)) {
try {
startWebAppV2();
LOG.info("Yarn WebApp UI 2 is started");
} catch (Exception e) {
LOG.error("Failed to start Yarn web app v2:" + e.getMessage());
}
}
startWepApp();
if (getConfig().getBoolean(YarnConfiguration.IS_MINI_YARN_CLUSTER,
false)) {

View File

@ -20,12 +20,12 @@
<parent>
<artifactId>hadoop-yarn</artifactId>
<groupId>org.apache.hadoop</groupId>
<version>3.0.0-SNAPSHOT</version>
<version>3.0.0-alpha1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-yarn-ui</artifactId>
<version>3.0.0-SNAPSHOT</version>
<version>3.0.0-alpha1-SNAPSHOT</version>
<name>Apache Hadoop YARN UI</name>
<packaging>${packaging.type}</packaging>

View File

@ -31,10 +31,8 @@ export default AbstractAdapter.extend({
urlForFindRecord(id, modelName, snapshot) {
var url = this._buildURL();
var url = url + '/apps/' +
return url + '/apps/' +
Converter.attemptIdToAppId(id) + "/appattempts/" + id;
console.log('app-attempt url:',url);
return url;
}
});

View File

@ -42,7 +42,7 @@ export default DS.RESTAdapter.extend({
var nodeHttpAddr = splits[0];
var containerId = splits[1];
var filename = splits[2];
this.host = this.host + nodeHttpAddr;
this.host = this.get('host') + nodeHttpAddr;
var url = this._buildURL();
url = url + "/containerlogs/" + containerId + "/" + filename;
return url;

View File

@ -25,14 +25,20 @@ export default AbstractAdapter.extend({
serverName: "NM",
urlForQuery(query) {
this.host = this.get("host") + query.nodeAddr;
var extension = this.get("host").split('/').pop();
if (extension != query.nodeAddr) {
this.host = this.get("host") + query.nodeAddr;
}
var url = this._buildURL();
url = url + "/apps";
return url;
},
urlForQueryRecord: function (query) {
this.host = this.get("host") + query.nodeAddr;
var extension = this.get("host").split('/').pop();
if (extension != query.nodeAddr) {
this.host = this.get("host") + query.nodeAddr;
}
var url = this._buildURL();
url = url + "/apps/" + query.appId;
return url;

View File

@ -25,14 +25,20 @@ export default AbstractAdapter.extend({
serverName: "NM",
urlForQuery(query) {
this.host = this.get("host") + query.nodeHttpAddr;
var extension = this.get("host").split('/').pop();
if (extension != query.nodeHttpAddr) {
this.host = this.get("host") + query.nodeHttpAddr;
}
var url = this._buildURL();
url = url + "/containers";
return url;
},
urlForQueryRecord(query) {
this.host = this.get("host") + query.nodeHttpAddr;
var extension = this.get("host").split('/').pop();
if (extension != query.nodeHttpAddr) {
this.host = this.get("host") + query.nodeHttpAddr;
}
var url = this._buildURL();
url = url + "/containers/" + query.containerId;
return url;

View File

@ -25,7 +25,10 @@ export default AbstractAdapter.extend({
serverName: "NM",
urlForFindRecord(id, modelName, snapshot) {
this.host = this.get("host") + id;
var extension = this.get("host").split('/').pop();
if (extension != id) {
this.host = this.get("host") + id;
}
var url = this._buildURL();
return url;
},

View File

@ -225,18 +225,27 @@ export default Ember.Component.extend({
didInsertElement: function() {
// init tooltip
this.initTooltip();
this.modelArr = [];
// init model
if (this.get("rmModel")) {
this.get("rmModel").forEach(function(o) {
this.modelArr.push(o);
if(!this.modelArr.contains(o)) {
this.modelArr.push(o);
}
}.bind(this));
}
if (this.get("tsModel")) {
this.get("tsModel").forEach(function(o) {
this.modelArr.push(o);
}.bind(this));
this.get("tsModel").forEach(function(o) {
if(!this.modelArr.contains(o)) {
this.modelArr.push(o);
}
}.bind(this));
}
if(this.modelArr.length == 0) {
return;
}
this.modelArr.sort(function(a, b) {

View File

@ -126,7 +126,7 @@ export default Ember.Component.extend({
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
.on("click", function(d,i){
if (d.queueData.get("name") != this.get("selected")) {
document.location.href = "yarn-queue/" + d.queueData.get("name");
document.location.href = "#/yarn-queue/" + d.queueData.get("name");
}
}.bind(this));
// .on("click", click);
@ -176,7 +176,7 @@ export default Ember.Component.extend({
.attr("r", 20)
.attr("href",
function(d) {
return "yarn-queues/" + d.queueData.get("name");
return "#/yarn-queues/" + d.queueData.get("name");
})
.style("stroke", function(d) {
if (d.queueData.get("name") == this.get("selected")) {

View File

@ -36,7 +36,7 @@ export default Ember.Helper.helper(function(params,hash) {
var html = '<td>';
var logFilesCommaSeparated = "";
for (var i = 0; i < logFilesLen; i++) {
html = html + '<a href="yarn-container-log/' + nodeId + '/' +
html = html + '<a href="#/yarn-container-log/' + nodeId + '/' +
nodeAddr + '/' + containerId + '/' + logFiles[i] + '">' + logFiles[i] +
'</a>';
if (i != logFilesLen - 1) {

View File

@ -29,7 +29,7 @@ export default Ember.Helper.helper(function(params,hash) {
if (nodeState == "SHUTDOWN" || nodeState == "LOST") {
html = html + nodeHTTPAddress;
} else {
html = html + '<a href="yarn-node/' + nodeId + "/" + nodeHTTPAddress + '">' +
html = html + '<a href="#/yarn-node/' + nodeId + "/" + nodeHTTPAddress + '">' +
nodeHTTPAddress + '</a>';
}
html = html + '</td>';

View File

@ -50,17 +50,17 @@ export default Ember.Helper.helper(function(params,hash) {
if (hash.path == 'yarn-node') {
html = html + ' class="active"';
}
html = html + '><a href="yarn-node/' + hash.nodeId + '/' + hash.nodeAddr +
html = html + '><a href="#/yarn-node/' + hash.nodeId + '/' + hash.nodeAddr +
'">Node Information</a></li><li';
if (hash.path == 'yarn-node-apps') {
html = html + ' class="active"';
}
html = html + '><a href="yarn-node-apps/' + hash.nodeId + '/' + hash.nodeAddr +
html = html + '><a href="#/yarn-node-apps/' + hash.nodeId + '/' + hash.nodeAddr +
'">List of Applications</a></li><li';
if (hash.path == 'yarn-node-containers') {
html = html + ' class="active"';
}
html = html + '><a href="yarn-node-containers/' +hash.nodeId + '/' + hash.nodeAddr +
html = html + '><a href="#/yarn-node-containers/' +hash.nodeId + '/' + hash.nodeAddr +
'">List of Containers</a></li></ul></ul></div>';
return Ember.String.htmlSafe(html);
});

View File

@ -0,0 +1,46 @@
/**
* 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.
*/
import Ember from 'ember';
export function nodeName(params/*, hash*/) {
// Place a menu within a panel inside col-md-2 container.
console.log('nodes-uid', params[0]);
var nodeIdSplitAtPort = params[0];
var portIndex = nodeIdSplitAtPort.indexOf(':');
if (portIndex != -1) {
nodeIdSplitAtPort = nodeIdSplitAtPort.substring(0, portIndex) +
':&#8203;' + nodeIdSplitAtPort.substring(portIndex + 1);
}
var normalizedNodeId = '';
var splitsAlongDots = nodeIdSplitAtPort.split('.');
if (splitsAlongDots) {
var len = splitsAlongDots.length;
for (var i = 0; i < len; i++) {
normalizedNodeId = normalizedNodeId + splitsAlongDots[i];
if (i != len - 1) {
normalizedNodeId = normalizedNodeId + '.&#8203;';
}
}
} else {
normalizedNodeId = nodeIdSplitAtPort;
}
return Ember.String.htmlSafe(normalizedNodeId);
}
export default Ember.Helper.helper(nodeName);

View File

@ -21,14 +21,29 @@ import Converter from 'yarn-ui/utils/converter';
export default DS.Model.extend({
startTime: DS.attr('string'),
startedTime: DS.attr('string'),
finishedTime: DS.attr('string'),
containerId: DS.attr('string'),
amContainerId: DS.attr('string'),
nodeHttpAddress: DS.attr('string'),
nodeId: DS.attr('string'),
hosts: DS.attr('string'),
logsLink: DS.attr('string'),
state: DS.attr('string'),
attemptStartedTime: function() {
var startTime = this.get("startTime");
// If startTime variable is not present, get from startedTime
if (startTime == undefined ||
startTime == "Invalid date") {
startTime = this.get("startedTime");
}
return startTime;
}.property("startedTime"),
startTs: function() {
return Converter.dateToTimeStamp(this.get("startTime"));
return Converter.dateToTimeStamp(this.get('attemptStartedTime'));
}.property("startTime"),
finishedTs: function() {
@ -36,11 +51,57 @@ export default DS.Model.extend({
return ts;
}.property("finishedTime"),
validatedFinishedTs: function() {
if (this.get("finishedTs") < this.get("startTs")) {
return "";
}
return this.get("finishedTime");
}.property("finishedTime"),
shortAppAttemptId: function() {
if (!this.get("containerId")) {
return this.get("id");
}
return "attempt_" +
parseInt(Converter.containerIdToAttemptId(this.get("containerId")).split("_")[3]);
}.property("containerId"),
appMasterContainerId: function() {
var id = this.get("containerId");
// If containerId variable is not present, get from amContainerId
if (id == undefined) {
id = this.get("amContainerId");
}
return id;
}.property("amContainerId"),
IsAmNodeUrl: function() {
var url = this.get("nodeHttpAddress");
// If nodeHttpAddress variable is not present, hardcode it.
if (url == undefined) {
url = "Not Available";
}
return url != "Not Available";
}.property("nodeHttpAddress"),
amNodeId : function() {
var id = this.get("nodeId");
// If nodeId variable is not present, get from host
if (id == undefined) {
id = this.get("hosts");
}
return id;
}.property("nodeId"),
IsLinkAvailable: function() {
var url = this.get("logsLink");
// If logsLink variable is not present, hardcode its.
if (url == undefined) {
url = "Not Available";
}
return url != "Not Available";
}.property("logsLink"),
elapsedTime: function() {
var elapsedMs = this.get("finishedTs") - this.get("startTs");
if (elapsedMs <= 0) {
@ -59,4 +120,13 @@ export default DS.Model.extend({
link: function() {
return "/yarn-app-attempt/" + this.get("id");
}.property(),
linkname: function() {
return "yarn-app-attempt";
}.property(),
attemptState: function() {
return this.get("state");
}.property(),
});

View File

@ -51,6 +51,13 @@ export default DS.Model.extend({
return this.get('finalStatus') == "FAILED"
}.property("finalStatus"),
validatedFinishedTs: function() {
if (this.get("finishedTime") < this.get("startTime")) {
return "";
}
return this.get("finishedTime");
}.property("finishedTime"),
allocatedResource: function() {
return Converter.resourceToString(this.get("allocatedMB"), this.get("allocatedVCores"));
}.property("allocatedMB", "allocatedVCores"),
@ -67,6 +74,13 @@ export default DS.Model.extend({
return "width: " + this.get("progress") + "%";
}.property("progress"),
runningContainersNumber: function() {
if(this.get("runningContainers") < 0) {
return 0;
}
return this.get("runningContainers");
}.property("progress"),
finalStatusStyle: function() {
var style = "default";
var finalStatus = this.get("finalStatus");

View File

@ -40,6 +40,13 @@ export default DS.Model.extend({
return ts;
}.property("finishedTime"),
validatedFinishedTs: function() {
if (this.get("finishedTs") < this.get("startTs")) {
return "";
}
return this.get("finishedTime");
}.property("finishedTime"),
elapsedTime: function() {
var elapsedMs = this.get("finishedTs") - this.get("startTs");
if (elapsedMs <= 0) {

View File

@ -33,7 +33,11 @@ export default Ember.Route.extend({
{
app_attempt_id: param.app_attempt_id,
is_rm: false
}),
}).catch (function() {
// Promise rejected, fulfill with some default value to
// use as the route's model and continue on with the transition
return [];
})
});
}
});

View File

@ -32,11 +32,14 @@ export default DS.JSONAPISerializer.extend({
type: primaryModelClass.modelName, // yarn-app
attributes: {
startTime: Converter.timeStampToDate(payload.startTime),
startedTime: Converter.timeStampToDate(payload.startedTime),
finishedTime: Converter.timeStampToDate(payload.finishedTime),
containerId: payload.containerId,
amContainerId: payload.amContainerId,
nodeHttpAddress: payload.nodeHttpAddress,
nodeId: payload.nodeId,
state: payload.nodeId,
hosts: payload.host,
state: payload.appAttemptState,
logsLink: payload.logsLink
}
};

View File

@ -75,10 +75,15 @@ export default DS.JSONAPISerializer.extend({
// payload has apps : { app: [ {},{},{} ] }
// need some error handling for ex apps or app may not be defined.
normalizedArrayResponse.data = payload.apps.app.map(singleApp => {
return this.internalNormalizeSingleResponse(store, primaryModelClass,
if(payload.apps) {
normalizedArrayResponse.data = payload.apps.app.map(singleApp => {
return this.internalNormalizeSingleResponse(store, primaryModelClass,
singleApp, singleApp.id, requestType);
}, this);
}, this);
} else {
normalizedArrayResponse.data = [];
}
return normalizedArrayResponse;
}
});

View File

@ -64,9 +64,10 @@ export default DS.JSONAPISerializer.extend({
singleContainer, singleContainer.id, requestType);
}, this);
return normalizedArrayResponse;
} else {
normalizedArrayResponse.data = [];
}
normalizedArrayResponse.data = [];
return normalizedArrayResponse;
}
});

View File

@ -76,10 +76,7 @@ export default DS.JSONAPISerializer.extend({
} else {
// No container reported inside containers.
// Response of the form { "apps": null }
normalizedArrayResponse.data = Ember.makeArray({
id: "dummy",
type: primaryModelClass.modelName,
attributes: {}});
normalizedArrayResponse.data = [];
}
return normalizedArrayResponse;
}

View File

@ -64,10 +64,7 @@ export default DS.JSONAPISerializer.extend({
} else {
// No container reported inside containers.
// Response of the form { "containers": null }
normalizedArrayResponse.data = Ember.makeArray({
id: "dummy",
type: primaryModelClass.modelName,
attributes: {}});
normalizedArrayResponse.data = [];
}
return normalizedArrayResponse;
}

View File

@ -67,10 +67,7 @@ export default DS.JSONAPISerializer.extend({
singleNode, singleNode.id);
}, this);
} else {
normalizedArrayResponse.data = Ember.makeArray({
id: "dummy",
type: primaryModelClass.modelName,
attributes: {}});
normalizedArrayResponse.data = [];
}
return normalizedArrayResponse;
}

View File

@ -32,7 +32,26 @@
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
{{outputMainMenu}}
{{#link-to 'yarn-queue' 'root' tagName="li"}}
{{#link-to 'yarn-queue' 'root'}}Queues
<span class="sr-only">(current)</span>
{{/link-to}}
{{/link-to}}
{{#link-to 'yarn-apps' tagName="li"}}
{{#link-to 'yarn-apps'}}Applications
<span class="sr-only">(current)</span>
{{/link-to}}
{{/link-to}}
{{#link-to 'cluster-overview' tagName="li"}}
{{#link-to 'cluster-overview'}}Cluster Overview
<span class="sr-only">(current)</span>
{{/link-to}}
{{/link-to}}
{{#link-to 'yarn-nodes' tagName="li"}}
{{#link-to 'yarn-nodes'}}Nodes
<span class="sr-only">(current)</span>
{{/link-to}}
{{/link-to}}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->

View File

@ -24,23 +24,39 @@
</tr>
<tr>
<td>Start Time</td>
<td>{{attempt.startTime}}</td>
<td>{{attempt.attemptStartedTime}}</td>
</tr>
<tr>
<td>AM Container Id</td>
<td>{{attempt.containerId}}</td>
<td>{{attempt.appMasterContainerId}}</td>
</tr>
{{#if attempt.IsAmNodeUrl}}
<tr>
<td>AM Node Web UI</td>
<td><a href={{attempt.nodeHttpAddress}}>{{attempt.nodeHttpAddress}}</a></td>
</tr>
{{/if}}
<tr>
<td>AM Node Id</td>
<td>{{attempt.nodeId}}</td>
<td>{{attempt.amNodeId}}</td>
</tr>
{{#if attempt.IsLinkAvailable}}
<tr>
<td>Log</td>
<td><a href={{attempt.logsLink}}>link</a></td>
</tr>
{{/if}}
{{#if attempt.attemptState}}
<tr>
<td>Attempt State</td>
<td>{{attempt.attemptState}}</td>
</tr>
{{/if}}
{{#if attempt.elapsedTime}}
<tr>
<td>Elapsed Time</td>
<td>{{attempt.elapsedTime}}</td>
</tr>
{{/if}}
</tbody>
</table>

View File

@ -36,7 +36,7 @@
{{#if arr}}
{{#each arr as |app|}}
<tr>
<td><a href="yarn-app/{{app.id}}">{{app.id}}</a></td>
<td><a href="#/yarn-app/{{app.id}}">{{app.id}}</a></td>
<td>{{app.appName}}</td>
<td>{{app.user}}</td>
<td>{{app.queue}}</td>
@ -44,7 +44,7 @@
<td><span class={{app.finalStatusStyle}}>{{app.finalStatus}}</span></td>
<td>{{app.startTime}}</td>
<td>{{app.elapsedTime}}</td>
<td>{{app.finishedTime}}</td>
<td>{{app.validatedFinishedTs}}</td>
<td>{{app.priority}}</td>
<td>
<div class="progress" style="margin-bottom: 0;">
@ -57,7 +57,7 @@
{{/each}}
{{else}}
<tr>
<td><a href="yarn-app/{{app.id}}">{{app.id}}</a></td>
<td><a href="#/yarn-app/{{app.id}}">{{app.id}}</a></td>
<td>{{app.appName}}</td>
<td>{{app.user}}</td>
<td>{{app.queue}}</td>
@ -65,7 +65,7 @@
<td><span class={{app.finalStatusStyle}}>{{app.finalStatus}}</span></td>
<td>{{app.startTime}}</td>
<td>{{app.elapsedTime}}</td>
<td>{{app.finishedTime}}</td>
<td>{{app.validatedFinishedTs}}</td>
<td>{{app.priority}}</td>
<td>
<div class="progress" style="margin-bottom: 0;">

View File

@ -24,7 +24,7 @@
</tr>
<tr>
<td>Finished Time</td>
<td>{{container.finishedTime}}</td>
<td>{{container.validatedFinishedTs}}</td>
</tr>
<tr>
<td>Elapsed Time</td>
@ -48,7 +48,7 @@
</tr>
<tr>
<td>NodeManager UI</td>
<td>{{container.nodeHttpAddress}}</td>
<td><a href={{container.nodeHttpAddress}}>{{container.nodeHttpAddress}}</a></td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1,44 @@
{{!
* 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.
}}
<div class="col-md-2 container-fluid">
<div class="panel panel-default">
<div class="panel-heading">
<h4>Node Manager<br>({{node-name nodeId}})</h4>
</div>
<div class="panel-body">
<ul class="nav nav-pills nav-stacked" id="stacked-menu">
<ul class="nav nav-pills nav-stacked collapse in">
{{#link-to 'yarn-node' tagName="li"}}
{{#link-to 'yarn-node' nodeId nodeAddr}}Node Information
{{/link-to}}
{{/link-to}}
{{#link-to 'yarn-node-apps' tagName="li"}}
{{#link-to 'yarn-node-apps' nodeId nodeAddr}}List of Applications
{{/link-to}}
{{/link-to}}
{{#link-to 'yarn-node-containers' tagName="li"}}
{{#link-to 'yarn-node-containers' nodeId nodeAddr}}List of Containers
{{/link-to}}
{{/link-to}}
</ul>
</ul>
</div>
</div>
</div>
{{outlet}}

View File

@ -34,7 +34,7 @@
<div class="panel panel-default">
<div class="panel-heading">
{{#if selected.link}}
<a href={{selected.link}}>{{selected.id}}</a>
{{#link-to selected.linkname selected.id}}{{selected.id}}{{/link-to}}
{{else}}
{{selected.id}}
{{/if}}

View File

@ -16,4 +16,4 @@
limitations under the License.
--}}
<h3 align = "center">Sorry, Error Occured.</h3>
<h3 align = "center">Sorry, Error Occurred.</h3>

View File

@ -18,12 +18,16 @@
<div class="container-fluid">
<div class="row">
{{#if model.attempt}}
{{app-attempt-table attempt=model.attempt}}
{{/if}}
</div>
<!-- containers table -->
<div class="row">
{{#if (or model.rmContainers model.tsContainers)}}
{{timeline-view parent-id="containers-timeline-div" my-id="timeline-view" height="400" rmModel=model.rmContainers tsModel=model.tsContainers label="shortAppAttemptId" attemptModel=false}}
{{/if}}
</div>
</div>

View File

@ -61,7 +61,7 @@
</tr>
<tr>
<td>Running Containers</td>
<td>{{model.app.runningContainers}}</td>
<td>{{model.app.runningContainersNumber}}</td>
</tr>
<tr>
<td>Preempted Resource</td>

View File

@ -15,7 +15,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
}}
{{app-table table-id="apps-table" arr=model}}
{{simple-table table-id="apps-table" bFilter=true colsOrder="0,desc" colTypes="natural elapsed-time" colTargets="0 7"}}
{{#if model}}
{{app-table table-id="apps-table" arr=model}}
{{simple-table table-id="apps-table" bFilter=true colsOrder="0,desc" colTypes="natural elapsed-time" colTargets="0 7"}}
{{else}}
<h4 align = "center">Could not find any applications from this cluster</h4>
{{/if}}
{{outlet}}

View File

@ -18,7 +18,7 @@
<div class="col-md-12 container-fluid">
<div class="row">
{{node-menu path="yarn-node-app" nodeAddr=model.nodeInfo.addr nodeId=model.nodeInfo.id}}
{{node-menu-panel path="yarn-node-app" nodeAddr=model.nodeInfo.addr nodeId=model.nodeInfo.id}}
<div class="col-md-10 container-fluid">
<div class="panel panel-default">
<div class="panel-heading"><b>Application Information</b></div>
@ -48,7 +48,7 @@
<tbody>
{{#each model.nodeApp.containers as |container|}}
<tr>
<td><a href="yarn-node-container/{{model.nodeInfo.id}}/{{model.nodeInfo.addr}}/{{container}}">{{container}}</a></td>
<td><a href="#/yarn-node-container/{{model.nodeInfo.id}}/{{model.nodeInfo.addr}}/{{container}}">{{container}}</a></td>
</tr>
{{/each}}
</tbody>

View File

@ -18,7 +18,8 @@
<div class="col-md-12 container-fluid">
<div class="row">
{{node-menu path="yarn-node-apps" nodeAddr=model.nodeInfo.addr nodeId=model.nodeInfo.id}}
{{node-menu-panel path="yarn-node-apps" nodeAddr=model.nodeInfo.addr nodeId=model.nodeInfo.id}}
{{#if model.apps}}
<div class="col-md-10 container-fluid">
<table id="node-apps-table" class="display table table-striped table-bordered" cellspacing="0" width="100%">
<thead>
@ -31,21 +32,20 @@
<tbody>
{{#if model.apps}}
{{#each model.apps as |app|}}
{{#if app.isDummyApp}}
<tr><td colspan="3" align="center">No apps found on this node</td></tr>
{{else}}
<tr>
<td><a href="yarn-node-app/{{model.nodeInfo.id}}/{{model.nodeInfo.addr}}/{{app.appId}}">{{app.appId}}</a></td>
<td><a href="#/yarn-node-app/{{model.nodeInfo.id}}/{{model.nodeInfo.addr}}/{{app.appId}}">{{app.appId}}</a></td>
<td><span class={{app.appStateStyle}}>{{app.state}}</span></td>
<td>{{app.user}}</td>
</tr>
{{/if}}
{{/each}}
{{/if}}
</tbody>
</table>
{{simple-table table-id="node-apps-table" bFilter=true colsOrder="0,desc" colTypes="natural" colTargets="0"}}
</div>
{{else}}
<h4 align = "center">No apps found on this node</h4>
{{/if}}
</div>
</div>
{{outlet}}

View File

@ -18,7 +18,7 @@
<div class="col-md-12 container-fluid">
<div class="row">
{{node-menu path="yarn-node-container" nodeAddr=model.nodeInfo.addr nodeId=model.nodeInfo.id}}
{{node-menu-panel path="yarn-node-container" nodeAddr=model.nodeInfo.addr nodeId=model.nodeInfo.id}}
<div class="col-md-10 container-fluid">
<div class="panel panel-default">
<div class="panel-heading"><b>Container Information</b></div>

View File

@ -18,7 +18,8 @@
<div class="col-md-12 container-fluid">
<div class="row">
{{node-menu path="yarn-node-containers" nodeAddr=model.nodeInfo.addr nodeId=model.nodeInfo.id}}
{{node-menu-panel path="yarn-node-containers" nodeAddr=model.nodeInfo.addr nodeId=model.nodeInfo.id}}
{{#if model.containers}}
<div class="col-md-10 container-fluid">
<table id="node-containers-table" class="display table table-striped table-bordered" cellspacing="0" width="100%">
<thead>
@ -32,11 +33,8 @@
<tbody>
{{#if model.containers}}
{{#each model.containers as |container|}}
{{#if container.isDummyContainer}}
<tr><td colspan="4" align="center">No containers found on this node</td></tr>
{{else}}
<tr>
<td><a href="yarn-node-container/{{model.nodeInfo.id}}/{{model.nodeInfo.addr}}/{{container.containerId}}">{{container.containerId}}</a></td>
<td><a href="#/yarn-node-container/{{model.nodeInfo.id}}/{{model.nodeInfo.addr}}/{{container.containerId}}">{{container.containerId}}</a></td>
<td><span class={{container.containerStateStyle}}>{{container.state}}</span></td>
<td>{{container.user}}</td>
<td>
@ -46,13 +44,15 @@
logFiles=container.containerLogFiles}}
</td>
</tr>
{{/if}}
{{/each}}
{{/if}}
</tbody>
</table>
{{simple-table table-id="node-containers-table" bFilter=true colsOrder="0,desc" colTypes="natural" colTargets="0"}}
</div>
{{else}}
<h4 align = "center">No containers found on this node</h4>
{{/if}}
</div>
</div>
{{outlet}}

View File

@ -18,7 +18,7 @@
<div class="col-md-12 container-fluid">
<div class="row">
{{node-menu path="yarn-node" nodeId=model.rmNode.id nodeAddr=model.node.id}}
{{node-menu-panel path="yarn-node" nodeId=model.rmNode.id nodeAddr=model.node.id}}
<div class="col-md-10 container-fluid">
<div class="panel panel-default">
<div class="panel-heading">Node Information</div>

View File

@ -16,6 +16,7 @@
limitations under the License.
--}}
{{#if model}}
<table id="nodes-table" class="display table table-striped table-bordered" cellspacing="0" width="100%">
<thead>
<tr>
@ -35,11 +36,7 @@
</tr>
</thead>
<tbody>
{{#if model}}
{{#each model as |node|}}
{{#if node.isDummyNode}}
<tr><td colspan="13" align="center">No nodes found on this cluster</td></tr>
{{else}}
<tr>
<td>{{node.nodeLabelsAsString}}</td>
<td>{{node.rack}}</td>
@ -55,11 +52,12 @@
<td>{{node.availableVirtualCores}}</td>
<td>{{node.version}}</td>
</tr>
{{/if}}
{{/each}}
{{/if}}
</tbody>
</table>
{{simple-table table-id="nodes-table" bFilter=true}}
{{else}}
<h4 align = "center">No nodes found on this cluster</h4>
{{/if}}
{{outlet}}

View File

@ -58,8 +58,12 @@
<div class="row">
<div class="col-md-12 container-fluid">
{{app-table table-id="apps-table" arr=model.apps}}
{{simple-table table-id="apps-table" bFilter=true colTypes="elapsed-time" colTargets="7"}}
{{#if model.apps}}
{{app-table table-id="apps-table" arr=model.apps}}
{{simple-table table-id="apps-table" bFilter=true colTypes="elapsed-time" colTargets="7"}}
{{else}}
<h4 align = "center">Could not find any applications from this cluster</h4>
{{/if}}
</div>
</div>

View File

@ -25,7 +25,7 @@ module.exports = function(environment) {
modulePrefix: 'yarn-ui',
environment: environment,
baseURL: '/',
locationType: 'auto',
locationType: 'hash',
EmberENV: {
FEATURES: {
// Here you can enable experimental features on an ember canary build

View File

@ -22,6 +22,7 @@
"broccoli-asset-rev": "2.4.2",
"broccoli-funnel": "1.0.1",
"ember-bootstrap": "0.5.1",
"ember-array-contains-helper": "1.0.2",
"ember-cli": "1.13.13",
"ember-cli-app-version": "1.0.0",
"ember-cli-babel": "5.1.6",
@ -42,6 +43,7 @@
"ember-export-application-global": "1.0.5",
"ember-resolver": "2.0.3",
"ember-spin-spinner": "0.2.3",
"ember-truth-helpers": "1.2.0",
"select2": "4.0.0"
}
}

View File

@ -0,0 +1,28 @@
/**
* 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.
*/
import { nodeName } from '../../../helpers/node-name';
import { module, test } from 'qunit';
module('Unit | Helper | node name');
// Replace this with your real tests.
test('it works', function(assert) {
let result = nodeName(42);
assert.ok(result);
});