HDFS-15245. Improve JournalNode web UI. Contributed by Jianfei Jiang.

This commit is contained in:
Ayush Saxena 2020-03-30 02:40:25 +05:30
parent 3eeb2466e9
commit 960c9ebaea
6 changed files with 253 additions and 45 deletions

View File

@ -19,8 +19,10 @@ package org.apache.hadoop.hdfs.qjournal.server;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.hadoop.util.VersionInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hadoop.classification.InterfaceAudience;
@ -57,7 +59,9 @@ import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* The JournalNode is a daemon which allows namenodes using
@ -392,7 +396,25 @@ public class JournalNode implements Tool, Configurable, JournalNodeMXBean {
return JSON.toString(status);
}
@Override // JournalNodeMXBean
public String getHostAndPort() {
return NetUtils.getHostPortString(rpcServer.getAddress());
}
@Override // JournalNodeMXBean
public List<String> getClusterIds() {
return journalsById.values().stream()
.map(j -> j.getStorage().getClusterID())
.filter(cid -> !Strings.isNullOrEmpty(cid))
.distinct().collect(Collectors.toList());
}
@Override // JournalNodeMXBean
public String getVersion() {
return VersionInfo.getVersion() + ", r" + VersionInfo.getRevision();
}
/**
* Register JournalNodeMXBean
*/

View File

@ -20,6 +20,8 @@ package org.apache.hadoop.hdfs.qjournal.server;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import java.util.List;
/**
* This is the JMX management interface for JournalNode information
*/
@ -32,5 +34,27 @@ public interface JournalNodeMXBean {
*
* @return A string presenting status for each journal
*/
public String getJournalsStatus();
String getJournalsStatus();
/**
* Get host and port of JournalNode.
*
* @return colon separated host and port.
*/
String getHostAndPort();
/**
* Get list of the clusters of JournalNode's journals
* as one JournalNode may support multiple clusters.
*
* @return list of clusters.
*/
List<String> getClusterIds();
/**
* Gets the version of Hadoop.
*
* @return the version of Hadoop.
*/
String getVersion();
}

View File

@ -1,5 +1,3 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@ -16,47 +14,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link rel="stylesheet" type="text/css" href="/static/bootstrap-3.4.1/css/bootstrap.min.css" />
<link rel="stylesheet" type="text/css" href="/static/hadoop.css" />
<title>JournalNode Information</title>
<meta http-equiv="REFRESH" content="0;url=journalnode.html" />
<title>Hadoop Administration</title>
</head>
<body>
<header class="navbar navbar-inverse bs-docs-nav" role="banner">
<div class="container">
<div class="navbar-header">
<div class="navbar-brand">Hadoop</div>
</div>
<ul class="nav navbar-nav" id="ui-tabs">
<li><a>Overview</a></li>
</ul>
</div>
</header>
<div class="container">
<div class="tab-content">
<div class="tab-pane" id="tab-overview">
<div class="page-header"><h1>JournalNode on <small><div id="authority" style="display: inline-block"></div></small></h1></div>
</div>
</div>
<div class="row">
<hr />
<div class="col-xs-2"><p>Hadoop, {release-year-token}.</p></div>
</div>
</div>
<script type="text/javascript" src="/static/jquery-3.4.1.min.js">
</script><script type="text/javascript" src="/static/bootstrap-3.4.1/js/bootstrap.min.js">
</script>
<script type="text/javascript">
$('#authority').html(window.location.host);
$('#tab-overview').addClass('active');
</script>
</body>
</html>

View File

@ -0,0 +1,82 @@
/**
* 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.
*/
(function () {
"use strict";
var data = {};
dust.loadSource(dust.compile($('#tmpl-jn').html(), 'jn'));
var BEANS = [
{"name": "jn", "url": "/jmx?qry=Hadoop:service=JournalNode,name=JournalNodeInfo"},
{"name": "journals", "url": "/jmx?qry=Hadoop:service=JournalNode,name=Journal-*"}
];
var HELPERS = {
'helper_date_tostring' : function (chunk, ctx, bodies, params) {
var value = dust.helpers.tap(params.value, chunk, ctx);
return chunk.write('' + moment(Number(value)).format('ddd MMM DD HH:mm:ss ZZ YYYY'));
}
};
load_json(
BEANS,
guard_with_startup_progress(function(d) {
for (var k in d) {
data[k] = k === 'journals' ? workaround(d[k].beans) : d[k].beans[0];
}
render();
}),
function (url, jqxhr, text, err) {
show_err_msg('<p>Failed to retrieve data from ' + url + ', cause: ' + err + '</p>');
});
function guard_with_startup_progress(fn) {
return function() {
try {
fn.apply(this, arguments);
} catch (err) {
if (err instanceof TypeError) {
show_err_msg('JournalNode error: ' + err);
}
}
};
}
function workaround(journals) {
for (var i in journals){
journals[i]['NameService']= journals[i]['modelerType'].split("-")[1];
}
return journals;
}
function render() {
var base = dust.makeBase(HELPERS);
dust.render('jn', base.push(data), function(err, out) {
$('#tab-overview').html(out);
$('#tab-overview').addClass('active');
});
}
function show_err_msg() {
$('#alert-panel-body').html("Failed to load journalnode information");
$('#alert-panel').show();
}
})();

View File

@ -0,0 +1,108 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!--
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.
-->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link rel="stylesheet" type="text/css" href="/static/bootstrap-3.4.1/css/bootstrap.min.css" />
<link rel="stylesheet" type="text/css" href="/static/hadoop.css" />
<title>JournalNode Information</title>
</head>
<body>
<header class="navbar navbar-inverse bs-docs-nav" role="banner">
<div class="container">
<div class="navbar-header">
<div class="navbar-brand">Hadoop</div>
</div>
<ul class="nav navbar-nav" id="ui-tabs">
<li><a href="#tab-overview">Overview</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Utilities <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="logs/">Logs</a></li>
<li><a href="logLevel">Log Level</a></li>
<li><a href="jmx">Metrics</a></li>
<li><a href="conf">Configuration</a></li>
<li><a href="stacks">Process Thread Dump</a></li>
</ul>
</li>
</ul>
</div>
</header>
<div class="container">
<div id="alert-panel">
<div class="alert alert-danger">
<button type="button" class="close" onclick="$('#alert-panel').hide();">&times;</button>
<div class="alert-body" id="alert-panel-body"></div>
</div>
</div>
<div class="tab-content">
<div class="tab-pane" id="tab-overview"></div>
</div>
<div class="row">
<hr />
<div class="col-xs-2"><p>Hadoop, {release-year-token}.</p></div>
</div>
</div>
<script type="text/x-dust-template" id="tmpl-jn">
{#jn}
<div class="page-header"><h1>JournalNode on <small>{HostAndPort}</small></h1></div>
<table class="table table-bordered table-striped">
<tr><th>Cluster ID:</th><td>{ClusterIds}</td></tr>
<tr><th>Version:</th><td>{Version}</td></tr>
</table>
{/jn}
<div class="page-header"><h1>Journal Status</h1></div>
<table class="table">
<thead>
<tr>
<th>Nameservice</th>
<th>Current Transaction ID</th>
<th>Current Lag Transactions</th>
<th>Last Journal Timestamp</th>
</tr>
</thead>
{#journals}
<tr>
<td>{NameService}</td>
<td>{LastWrittenTxId}</td>
<td>{CurrentLagTxns}</td>
<td>{#helper_date_tostring value="{LastJournalTimestamp}"/}</td>
</tr>
{/journals}
</table>
</script>
<script type="text/javascript" src="/static/jquery-3.4.1.min.js"></script>
<script type="text/javascript" src="/static/bootstrap-3.4.1/js/bootstrap.min.js"></script>
<script type="text/javascript" src="/static/moment.min.js"></script>
<script type="text/javascript" src="/static/dust-full-2.0.0.min.js"></script>
<script type="text/javascript" src="/static/dust-helpers-1.1.1.min.js"></script>
<script type="text/javascript" src="/static/dfs-dust.js"></script>
<script type="text/javascript" src="jn.js"></script>
</body>
</html>

View File

@ -96,7 +96,17 @@ public class TestJournalNodeMXBean {
infoMap1.put("Formatted", "false");
jMap.put(MiniJournalCluster.CLUSTER_WAITACTIVE_URI, infoMap1);
assertEquals(JSON.toString(jMap), journalStatus);
// check attributes
String hostAndPort = (String) mbs.getAttribute(mxbeanName, "HostAndPort");
assertEquals(jn.getHostAndPort(), hostAndPort);
assertTrue(hostAndPort.matches("localhost:\\d+"));
String[] clusterId = (String[]) mbs.getAttribute(mxbeanName, "ClusterIds");
assertEquals(jn.getClusterIds().size(), clusterId.length);
assertEquals("mycluster", clusterId[0]);
String version = (String) mbs.getAttribute(mxbeanName, "Version");
assertEquals(jn.getVersion(), version);
// restart journal node without formatting
jCluster = new MiniJournalCluster.Builder(new Configuration()).format(false)
.numJournalNodes(NUM_JN).build();