You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ja...@apache.org on 2018/08/08 11:40:27 UTC
lucene-solr:branch_7x: SOLR-7767: "ZK Status" sub menu under "Cloud"
tab to see status of zookeeper ensemble
Repository: lucene-solr
Updated Branches:
refs/heads/branch_7x b7f14648f -> 572557d8f
SOLR-7767: "ZK Status" sub menu under "Cloud" tab to see status of zookeeper ensemble
(cherry picked from commit 9306922)
Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/572557d8
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/572557d8
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/572557d8
Branch: refs/heads/branch_7x
Commit: 572557d8fd2a6857e0fc9ab47de40601182c0915
Parents: b7f1464
Author: Jan Høydahl <ja...@apache.org>
Authored: Wed Aug 8 12:43:19 2018 +0200
Committer: Jan Høydahl <ja...@apache.org>
Committed: Wed Aug 8 13:14:37 2018 +0200
----------------------------------------------------------------------
solr/CHANGES.txt | 2 +
.../org/apache/solr/core/CoreContainer.java | 3 +
.../handler/admin/ZookeeperStatusHandler.java | 222 +++++++++++++++++++
.../admin/ZookeeperStatusHandlerTest.java | 85 +++++++
solr/solr-ref-guide/src/cloud-screens.adoc | 5 +
.../src/images/cloud-screens/cloud-zkstatus.png | Bin 0 -> 175359 bytes
.../apache/solr/common/params/CommonParams.java | 1 +
solr/webapp/web/css/angular/cloud.css | 71 +++++-
solr/webapp/web/css/angular/menu.css | 1 +
solr/webapp/web/index.html | 1 +
solr/webapp/web/js/angular/controllers/cloud.js | 38 +++-
solr/webapp/web/js/angular/services.js | 6 +
solr/webapp/web/partials/cloud.html | 59 +++++
13 files changed, 491 insertions(+), 3 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/572557d8/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 5578ac3..68c8155 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -97,6 +97,8 @@ New Features
* SOLR-8207: Add "Nodes" view to the Admin UI "Cloud" tab, listing nodes and key metrics (janhoy)
+* SOLR-7767: "ZK Status" sub menu under "Cloud" tab to see status of zookeeper ensemble (janhoy)
+
* SOLR-11990: Make it possible to co-locate replicas of multiple collections together in a node. A collection may be
co-located with another collection during collection creation time by specifying a 'withCollection' parameter. It can
also be co-located afterwards by using the modify collection API. The co-location guarantee is enforced regardless of
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/572557d8/solr/core/src/java/org/apache/solr/core/CoreContainer.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 67b8794..cf51268 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -84,6 +84,7 @@ import org.apache.solr.handler.admin.SecurityConfHandler;
import org.apache.solr.handler.admin.SecurityConfHandlerLocal;
import org.apache.solr.handler.admin.SecurityConfHandlerZk;
import org.apache.solr.handler.admin.ZookeeperInfoHandler;
+import org.apache.solr.handler.admin.ZookeeperStatusHandler;
import org.apache.solr.handler.component.ShardHandlerFactory;
import org.apache.solr.logging.LogWatcher;
import org.apache.solr.logging.MDCLoggingContext;
@@ -118,6 +119,7 @@ import static org.apache.solr.common.params.CommonParams.INFO_HANDLER_PATH;
import static org.apache.solr.common.params.CommonParams.METRICS_HISTORY_PATH;
import static org.apache.solr.common.params.CommonParams.METRICS_PATH;
import static org.apache.solr.common.params.CommonParams.ZK_PATH;
+import static org.apache.solr.common.params.CommonParams.ZK_STATUS_PATH;
import static org.apache.solr.core.CorePropertiesLocator.PROPERTIES_FILENAME;
import static org.apache.solr.security.AuthenticationPlugin.AUTHENTICATION_PLUGIN_PROP;
@@ -561,6 +563,7 @@ public class CoreContainer {
this.backupRepoFactory = new BackupRepositoryFactory(cfg.getBackupRepositoryPlugins());
createHandler(ZK_PATH, ZookeeperInfoHandler.class.getName(), ZookeeperInfoHandler.class);
+ createHandler(ZK_STATUS_PATH, ZookeeperStatusHandler.class.getName(), ZookeeperStatusHandler.class);
collectionsHandler = createHandler(COLLECTIONS_HANDLER_PATH, cfg.getCollectionsHandlerClass(), CollectionsHandler.class);
infoHandler = createHandler(INFO_HANDLER_PATH, cfg.getInfoHandlerClass(), InfoHandler.class);
coreAdminHandler = createHandler(CORES_HANDLER_PATH, cfg.getCoreAdminHandlerClass(), CoreAdminHandler.class);
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/572557d8/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperStatusHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperStatusHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperStatusHandler.java
new file mode 100644
index 0000000..8842437
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperStatusHandler.java
@@ -0,0 +1,222 @@
+/*
+ * 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.solr.handler.admin;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.lang.invoke.MethodHandles;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.handler.RequestHandlerBase;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Zookeeper Status handler, talks to ZK using sockets and four-letter words
+ *
+ * @since solr 7.5
+ */
+public final class ZookeeperStatusHandler extends RequestHandlerBase {
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private static final int ZOOKEEPER_DEFAULT_PORT = 2181;
+ private static final String STATUS_RED = "red";
+ private static final String STATUS_GREEN = "green";
+ private static final String STATUS_YELLOW = "yellow";
+ private static final String STATUS_NA = "N/A";
+ private CoreContainer cores;
+
+ public ZookeeperStatusHandler(CoreContainer cc) {
+ this.cores = cc;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Fetch Zookeeper status";
+ }
+
+ @Override
+ public Category getCategory() {
+ return Category.ADMIN;
+ }
+
+ @Override
+ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+ final SolrParams params = req.getParams();
+ Map<String, String> map = new HashMap<>(1);
+ NamedList values = rsp.getValues();
+ values.add("zkStatus", getZkStatus(cores.getZkController().getZkServerAddress()));
+ }
+
+ /*
+ Gets all info from ZK API and returns as a map
+ */
+ protected Map<String, Object> getZkStatus(String zkHost) {
+ Map<String, Object> zkStatus = new HashMap<>();
+ List<String> zookeepers = Arrays.asList(zkHost.split("/")[0].split(","));
+ List<Object> details = new ArrayList<>();
+ int numOk = 0;
+ String status = STATUS_NA;
+ int standalone = 0;
+ int followers = 0;
+ int reportedFollowers = 0;
+ int leaders = 0;
+ List<String> errors = new ArrayList<>();
+ for (String zk : zookeepers) {
+ try {
+ Map<String, Object> stat = monitorZookeeper(zk);
+ details.add(stat);
+ if ("true".equals(String.valueOf(stat.get("ok")))) {
+ numOk++;
+ }
+ String state = String.valueOf(stat.get("zk_server_state"));
+ if ("follower".equals(state)) {
+ followers++;
+ } else if ("leader".equals(state)) {
+ leaders++;
+ reportedFollowers = Integer.parseInt(String.valueOf(stat.get("zk_followers")));
+ } else if ("standalone".equals(state)) {
+ standalone++;
+ }
+ } catch (SolrException se) {
+ log.warn("Failed talking to zookeeper" + zk, se);
+ errors.add(se.getMessage());
+ Map<String, Object> stat = new HashMap<>();
+ stat.put("host", zk);
+ stat.put("ok", false);
+ details.add(stat);
+ }
+ }
+ zkStatus.put("ensembleSize", zookeepers.size());
+ zkStatus.put("zkHost", zkHost);
+ zkStatus.put("details", details);
+ if (followers+leaders > 0 && standalone > 0) {
+ status = STATUS_RED;
+ errors.add("The zk nodes do not agree on their mode, check details");
+ }
+ if (standalone > 1) {
+ status = STATUS_RED;
+ errors.add("Only one zk allowed in standalone mode");
+ }
+ if (leaders > 1) {
+ zkStatus.put("mode", "ensemble");
+ status = STATUS_RED;
+ errors.add("Only one leader allowed, got " + leaders);
+ }
+ if (followers > 0 && leaders == 0) {
+ zkStatus.put("mode", "ensemble");
+ status = STATUS_RED;
+ errors.add("We do not have a leader");
+ }
+ if (leaders > 0 && followers != reportedFollowers) {
+ zkStatus.put("mode", "ensemble");
+ status = STATUS_RED;
+ errors.add("Leader reports " + reportedFollowers + " followers, but we only found " + followers +
+ ". Please check zkHost configuration");
+ }
+ if (followers+leaders == 0 && standalone == 1) {
+ zkStatus.put("mode", "standalone");
+ }
+ if (followers+leaders > 0 && (zookeepers.size())%2 == 0) {
+ if (!STATUS_RED.equals(status)) {
+ status = STATUS_YELLOW;
+ }
+ errors.add("We have an even number of zookeepers which is not recommended");
+ }
+ if (followers+leaders > 0 && standalone == 0) {
+ zkStatus.put("mode", "ensemble");
+ }
+ if (status.equals(STATUS_NA)) {
+ if (numOk == zookeepers.size()) {
+ status = STATUS_GREEN;
+ } else if (numOk < zookeepers.size() && numOk > zookeepers.size() / 2) {
+ status = STATUS_YELLOW;
+ errors.add("Some zookeepers are down: " + numOk + "/" + zookeepers.size());
+ } else {
+ status = STATUS_RED;
+ errors.add("Mismatch in number of zookeeper nodes live. numOK=" + numOk + ", expected " + zookeepers.size());
+ }
+ }
+ zkStatus.put("status", status);
+ if (!errors.isEmpty()) {
+ zkStatus.put("errors", errors);
+ }
+ return zkStatus;
+ }
+
+ private Map<String, Object> monitorZookeeper(String zkHostPort) {
+ List<String> lines = getZkRawResponse(zkHostPort, "mntr");
+ Map<String, Object> obj = new HashMap<>();
+ obj.put("host", zkHostPort);
+ obj.put("ok", "imok".equals(getZkRawResponse(zkHostPort, "ruok").get(0)));
+ for (String line : lines) {
+ obj.put(line.split("\t")[0], line.split("\t")[1]);
+ }
+ lines = getZkRawResponse(zkHostPort, "conf");
+ for (String line : lines) {
+ obj.put(line.split("=")[0], line.split("=")[1]);
+ }
+ return obj;
+ }
+
+ /**
+ * Sends a four-letter-word command to one particular Zookeeper server and returns the response as list of strings
+ * @param zkHostPort the host:port for one zookeeper server to access
+ * @param fourLetterWordCommand the custom 4-letter command to send to Zookeeper
+ * @return a list of lines returned from Zookeeper
+ */
+ private List<String> getZkRawResponse(String zkHostPort, String fourLetterWordCommand) {
+ String[] hostPort = zkHostPort.split(":");
+ String host = hostPort[0];
+ int port = ZOOKEEPER_DEFAULT_PORT;
+ if (hostPort.length > 1) {
+ port = Integer.parseInt(hostPort[1]);
+ }
+ try (
+ Socket socket = new Socket(host, port);
+ Writer writer = new OutputStreamWriter(socket.getOutputStream(), "utf-8");
+ PrintWriter out = new PrintWriter(writer, true);
+ BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));) {
+ out.println(fourLetterWordCommand);
+ List<String> response = in.lines().collect(Collectors.toList());
+ log.debug("Got response from ZK on host {} and port {}: {}", host, port, response);
+ if (response == null || response.isEmpty()) {
+ throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Empty response from Zookeeper " + zkHostPort);
+ }
+ return response;
+ } catch (IOException e) {
+ throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Failed talking to Zookeeper " + zkHostPort, e);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/572557d8/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperStatusHandlerTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperStatusHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperStatusHandlerTest.java
new file mode 100644
index 0000000..7ec8bf2
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperStatusHandlerTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.solr.handler.admin;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.impl.HttpSolrClient;
+import org.apache.solr.client.solrj.request.GenericSolrRequest;
+import org.apache.solr.client.solrj.response.DelegationTokenResponse;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.util.NamedList;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ZookeeperStatusHandlerTest extends SolrCloudTestCase {
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ configureCluster(1)
+ .addConfig("conf", configset("cloud-minimal"))
+ .configure();
+ }
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /*
+ Test the monitoring endpoint, used in the Cloud => ZkStatus Admin UI screen
+ NOTE: We do not currently test with multiple zookeepers, but the only difference is that there are multiple "details" objects and mode is "ensemble"...
+ */
+ @Test
+ public void monitorZookeeper() throws IOException, SolrServerException, InterruptedException, ExecutionException, TimeoutException {
+ URL baseUrl = cluster.getJettySolrRunner(0).getBaseUrl();
+ HttpSolrClient solr = new HttpSolrClient.Builder(baseUrl.toString()).build();
+ GenericSolrRequest mntrReq = new GenericSolrRequest(SolrRequest.METHOD.GET, "/admin/zookeeper/status", new ModifiableSolrParams());
+ mntrReq.setResponseParser(new DelegationTokenResponse.JsonMapResponseParser());
+ NamedList<Object> nl = solr.httpUriRequest(mntrReq).future.get(1000, TimeUnit.MILLISECONDS);
+
+ assertEquals("zkStatus", nl.getName(1));
+ Map<String,Object> zkStatus = (Map<String,Object>) nl.get("zkStatus");
+ assertEquals("green", zkStatus.get("status"));
+ assertEquals("standalone", zkStatus.get("mode"));
+ assertEquals(1L, zkStatus.get("ensembleSize"));
+ List<Object> detailsList = (List<Object>)zkStatus.get("details");
+ assertEquals(1, detailsList.size());
+ Map<String,Object> details = (Map<String,Object>) detailsList.get(0);
+ assertEquals(true, details.get("ok"));
+ assertTrue(Integer.parseInt((String) details.get("zk_znode_count")) > 50);
+ solr.close();
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/572557d8/solr/solr-ref-guide/src/cloud-screens.adoc
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/cloud-screens.adoc b/solr/solr-ref-guide/src/cloud-screens.adoc
index 34982ee..cc5a5eb 100644
--- a/solr/solr-ref-guide/src/cloud-screens.adoc
+++ b/solr/solr-ref-guide/src/cloud-screens.adoc
@@ -42,6 +42,11 @@ image::images/cloud-screens/cloud-tree.png[image,width=487,height=250]
As an aid to debugging, the data shown in the "Tree" view can be exported locally using the following command `bin/solr zk ls -r /`
+== ZK Status view
+The "ZK Status" view gives an overview over the Zookeepers used by Solr. It lists whether running in `standalone` or `ensemble` mode, shows how many zookeepers are configured, and then displays a table listing detailed monitoring status for each of the zookeepers, inlcuding who is the leader, configuration parameters and more.
+
+image::images/cloud-screens/cloud-zkstatus.png[image,width=512,height=509]
+
== Graph views
The "Graph" view shows a graph of each collection, the shards that make up those collections, and the addresses and type ("NRT", "TLOG" or "PULL") of each replica for each shard.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/572557d8/solr/solr-ref-guide/src/images/cloud-screens/cloud-zkstatus.png
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/images/cloud-screens/cloud-zkstatus.png b/solr/solr-ref-guide/src/images/cloud-screens/cloud-zkstatus.png
new file mode 100644
index 0000000..b1b7452
Binary files /dev/null and b/solr/solr-ref-guide/src/images/cloud-screens/cloud-zkstatus.png differ
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/572557d8/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java b/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java
index 600e479..28865d2 100644
--- a/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java
+++ b/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java
@@ -181,6 +181,7 @@ public interface CommonParams {
String AUTHZ_PATH = "/admin/authorization";
String AUTHC_PATH = "/admin/authentication";
String ZK_PATH = "/admin/zookeeper";
+ String ZK_STATUS_PATH = "/admin/zookeeper/status";
String SYSTEM_INFO_PATH = "/admin/info/system";
String METRICS_PATH = "/admin/metrics";
String METRICS_HISTORY_PATH = "/admin/metrics/history";
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/572557d8/solr/webapp/web/css/angular/cloud.css
----------------------------------------------------------------------
diff --git a/solr/webapp/web/css/angular/cloud.css b/solr/webapp/web/css/angular/cloud.css
index c3d54a5..5c8ce45 100644
--- a/solr/webapp/web/css/angular/cloud.css
+++ b/solr/webapp/web/css/angular/cloud.css
@@ -536,7 +536,8 @@ limitations under the License.
}
/* Styling of reload and details buttons */
-#content #cloud #controls
+#content #cloud #controls,
+#content #cloud #frame #zk-status-content #zk-controls
{
display: block;
height: 30px;
@@ -641,4 +642,70 @@ limitations under the License.
border-radius: 4px;
background-color: rgba(0,0,0,.5);
-webkit-box-shadow: 0 0 1px rgba(255,255,255,.5);
-}
\ No newline at end of file
+}
+#content #cloud #zk-table td,
+#content #cloud #zk-table th
+{
+ border: 0px solid #ddd;
+ border-bottom: 0.50px solid #eee;
+ padding-right: 5px;
+ padding-left: 5px;
+}
+
+#content #cloud #zk-table th
+{
+ border-bottom: 1px solid #ddd;
+ border-top: 1px solid #ddd;
+ font-weight: bolder;
+ font-stretch: extra-expanded;
+ background: #F8F8F8;
+}
+
+#content #cloud #zk-table
+{
+ border-top: 1px solid #c0c0c0;
+ margin-top: 10px;
+ border-collapse: collapse;
+
+ font-weight: bold;
+}
+
+#content #cloud #zk-table #detail-divider
+{
+ background-color: #f8f8f8;
+ height: 10px;
+}
+
+.zookeeper-status
+{
+ font-size: large;
+}
+
+.zookeeper-errors
+{
+ background-color: lightpink;
+ padding: 10px;
+ border: 1px;
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+.zookeeper-errors li::before
+{
+ content: "- ";
+}
+
+.zkstatus-green
+{
+ color: darkgreen;
+}
+
+.zkstatus-yellow
+{
+ color: orange;
+}
+
+.zkstatus-red
+{
+ color: red;
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/572557d8/solr/webapp/web/css/angular/menu.css
----------------------------------------------------------------------
diff --git a/solr/webapp/web/css/angular/menu.css b/solr/webapp/web/css/angular/menu.css
index ba5e0b6..f4e04c1 100644
--- a/solr/webapp/web/css/angular/menu.css
+++ b/solr/webapp/web/css/angular/menu.css
@@ -261,6 +261,7 @@ limitations under the License.
#menu #cloud.global p a { background-image: url( ../../img/ico/network-cloud.png ); }
#menu #cloud.global .tree a { background-image: url( ../../img/ico/folder-tree.png ); }
#menu #cloud.global .nodes a { background-image: url( ../../img/solr-ico.png ); }
+#menu #cloud.global .zkstatus a { background-image: url( ../../img/ico/node-master.png ); }
#menu #cloud.global .graph a { background-image: url( ../../img/ico/molecule.png ); }
#menu #cloud.global .rgraph a { background-image: url( ../../img/ico/asterisk.png ); }
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/572557d8/solr/webapp/web/index.html
----------------------------------------------------------------------
diff --git a/solr/webapp/web/index.html b/solr/webapp/web/index.html
index 256af89..0663805 100644
--- a/solr/webapp/web/index.html
+++ b/solr/webapp/web/index.html
@@ -152,6 +152,7 @@ limitations under the License.
<ul ng-show="showingCloud">
<li class="nodes" ng-class="{active:page=='cloud-nodes'}"><a href="#/~cloud?view=nodes">Nodes</a></li>
<li class="tree" ng-class="{active:page=='cloud-tree'}"><a href="#/~cloud?view=tree">Tree</a></li>
+ <li class="zkstatus" ng-class="{active:page=='cloud-zkstatus'}"><a href="#/~cloud?view=zkstatus">ZK Status</a></li>
<li class="graph" ng-class="{active:page=='cloud-graph'}"><a href="#/~cloud?view=graph">Graph</a></li>
<li class="rgraph" ng-class="{active:page=='cloud-rgraph'}"><a href="#/~cloud?view=rgraph">Graph (Radial)</a></li>
</ul>
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/572557d8/solr/webapp/web/js/angular/controllers/cloud.js
----------------------------------------------------------------------
diff --git a/solr/webapp/web/js/angular/controllers/cloud.js b/solr/webapp/web/js/angular/controllers/cloud.js
index 08eea38..abe0ef6 100644
--- a/solr/webapp/web/js/angular/controllers/cloud.js
+++ b/solr/webapp/web/js/angular/controllers/cloud.js
@@ -16,7 +16,7 @@
*/
solrAdminApp.controller('CloudController',
- function($scope, $location, Zookeeper, Constants, Collections, System, Metrics) {
+ function($scope, $location, Zookeeper, Constants, Collections, System, Metrics, ZookeeperStatus) {
$scope.showDebug = false;
@@ -41,6 +41,9 @@ solrAdminApp.controller('CloudController',
} else if (view === "nodes") {
$scope.resetMenu("cloud-nodes", Constants.IS_ROOT_PAGE);
nodesSubController($scope, Collections, System, Metrics);
+ } else if (view === "zkstatus") {
+ $scope.resetMenu("cloud-zkstatus", Constants.IS_ROOT_PAGE);
+ zkStatusSubController($scope, ZookeeperStatus, false);
}
}
);
@@ -486,7 +489,39 @@ var nodesSubController = function($scope, Collections, System, Metrics) {
$scope.initClusterState();
};
+var zkStatusSubController = function($scope, ZookeeperStatus) {
+ $scope.showZkStatus = true;
+ $scope.showNodes = false;
+ $scope.showTree = false;
+ $scope.showGraph = false;
+ $scope.tree = {};
+ $scope.showData = false;
+ $scope.showDetails = false;
+
+ $scope.toggleDetails = function() {
+ $scope.showDetails = !$scope.showDetails === true;
+ };
+
+ $scope.initZookeeper = function() {
+ ZookeeperStatus.monitor({}, function(data) {
+ $scope.zkState = data.zkStatus;
+ $scope.mainKeys = ["ok", "clientPort", "zk_server_state", "zk_version",
+ "zk_approximate_data_size", "zk_znode_count", "zk_num_alive_connections"];
+ $scope.detailKeys = ["dataDir", "dataLogDir",
+ "zk_avg_latency", "zk_max_file_descriptor_count", "zk_watch_count",
+ "zk_packets_sent", "zk_packets_received",
+ "tickTime", "maxClientCnxns", "minSessionTimeout", "maxSessionTimeout"];
+ $scope.ensembleMainKeys = ["serverId", "electionPort", "quorumPort"];
+ $scope.ensembleDetailKeys = ["peerType", "electionAlg", "initLimit", "syncLimit",
+ "zk_followers", "zk_synced_followers", "zk_pending_syncs"];
+ });
+ };
+
+ $scope.initZookeeper();
+};
+
var treeSubController = function($scope, Zookeeper) {
+ $scope.showZkStatus = false;
$scope.showTree = true;
$scope.showGraph = false;
$scope.tree = {};
@@ -545,6 +580,7 @@ function secondsForHumans ( seconds ) {
}
var graphSubController = function ($scope, Zookeeper, isRadial) {
+ $scope.showZkStatus = false;
$scope.showTree = false;
$scope.showGraph = true;
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/572557d8/solr/webapp/web/js/angular/services.js
----------------------------------------------------------------------
diff --git a/solr/webapp/web/js/angular/services.js b/solr/webapp/web/js/angular/services.js
index 66f2654..e3dcd3f 100644
--- a/solr/webapp/web/js/angular/services.js
+++ b/solr/webapp/web/js/angular/services.js
@@ -81,6 +81,12 @@ solrAdminServices.factory('System',
}}
});
}])
+.factory('ZookeeperStatus',
+ ['$resource', function($resource) {
+ return $resource('admin/zookeeper/status', {wt:'json', _:Date.now()}, {
+ "monitor": {}
+ });
+ }])
.factory('Properties',
['$resource', function($resource) {
return $resource('admin/info/properties', {'wt':'json', '_':Date.now()});
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/572557d8/solr/webapp/web/partials/cloud.html
----------------------------------------------------------------------
diff --git a/solr/webapp/web/partials/cloud.html b/solr/webapp/web/partials/cloud.html
index 078c9af..825e195 100644
--- a/solr/webapp/web/partials/cloud.html
+++ b/solr/webapp/web/partials/cloud.html
@@ -18,6 +18,65 @@ limitations under the License.
<div id="frame">
+ <div id="zk-status-content" class="content clearfix" ng-show="showZkStatus">
+ <div id="zk-controls">
+ <a class="reload" ng-click="initZookeeper()"><span>Refresh</span></a>
+ <a class="details-button" ng-click="toggleDetails()" ng-class="{on:showDetails}">
+ <span>Toggle details</span>
+ </a>
+ </div>
+
+ <div class="zookeeper-status">Status: <span class="zkstatus-{{zkState.status}}">{{zkState.status}}</span></div>
+ <div class="zookeeper-errors" ng-show="zkState.errors">
+ Errors:
+ <ul>
+ <li ng-repeat="error in zkState.errors">{{error}}</li>
+ </ul>
+ </div>
+ <div>ZK connection string: {{zkState.zkHost}}</div>
+ <div>Ensemble size: {{zkState.ensembleSize}}</div>
+ <div>Ensemble mode: {{zkState.mode}}</div>
+
+ <table id="zk-table">
+ <thead>
+ <tr>
+ <th></th>
+ <th ng-repeat="host in zkState.details">{{host.host}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="key in mainKeys">
+ <td>{{key}}</td>
+ <td ng-repeat="host in zkState.details" ng-style="key === 'zk_server_state' && host[key] === 'leader' ? {'font-weight': 'bold'} : {'font-weight': 'normal'}">
+ {{key === 'zk_version' ? host[key].split("-")[0] : host[key]}}
+ </td>
+ </tr>
+ <tr ng-repeat="key in ensembleMainKeys" ng-show="zkState.mode === 'ensemble'">
+ <td>{{key}}</td>
+ <td ng-repeat="host in zkState.details">
+ {{host[key]}}
+ </td>
+ </tr>
+ <tr id="detail-divider" ng-show="showDetails" >
+ <td ng-class="details"></td>
+ <td ng-repeat="host in zkState.details" ng-class="details"></td>
+ </tr>
+ <tr ng-repeat="key in detailKeys" ng-show="showDetails">
+ <td>{{key}}</td>
+ <td ng-repeat="host in zkState.details">
+ {{host[key]}}
+ </td>
+ </tr>
+ <tr ng-repeat="key in ensembleDetailKeys" ng-show="showDetails && zkState.mode === 'ensemble'">
+ <td>{{key}}</td>
+ <td ng-repeat="host in zkState.details">
+ {{host[key]}}
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
<div id="tree-content" class="content clearfix" ng-show="showTree">
<jstree class="tree" on-select="showTreeLink(url)" id="tree" data="tree"></jstree>