Fix precommit

Remove errors from each host detail map
Display secureClientPort and server.1, server.2, server.3...
Added test for various failure responses and expected result from multiple nodes
This commit is contained in:
Jan Høydahl 2019-08-02 15:03:40 +02:00
parent 1123afae94
commit 9548481c8c
4 changed files with 104 additions and 18 deletions

View File

@ -28,6 +28,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
@ -46,7 +47,7 @@ import org.slf4j.LoggerFactory;
*
* @since solr 7.5
*/
public final class ZookeeperStatusHandler extends RequestHandlerBase {
public class ZookeeperStatusHandler extends RequestHandlerBase {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final int ZOOKEEPER_DEFAULT_PORT = 2181;
@ -101,6 +102,7 @@ public final class ZookeeperStatusHandler extends RequestHandlerBase {
Map<String, Object> stat = monitorZookeeper(zk);
if (stat.containsKey("errors")) {
errors.addAll((List<String>)stat.get("errors"));
stat.remove("errors");
}
details.add(stat);
if ("true".equals(String.valueOf(stat.get("ok")))) {
@ -116,7 +118,7 @@ public final class ZookeeperStatusHandler extends RequestHandlerBase {
standalone++;
}
} catch (SolrException se) {
log.warn("Failed talking to zookeeper" + zk, se);
log.warn("Failed talking to zookeeper " + zk, se);
errors.add(se.getMessage());
Map<String, Object> stat = new HashMap<>();
stat.put("host", zk);
@ -180,31 +182,34 @@ public final class ZookeeperStatusHandler extends RequestHandlerBase {
return zkStatus;
}
private Map<String, Object> monitorZookeeper(String zkHostPort) throws SolrException {
protected Map<String, Object> monitorZookeeper(String zkHostPort) throws SolrException {
Map<String, Object> obj = new HashMap<>();
List<String> errors = new ArrayList<>();
obj.put("host", zkHostPort);
List<String> lines = getZkRawResponse(zkHostPort, "ruok");
validateZkRawResponse(lines, zkHostPort,"ruok");
boolean ok = "imok".equals(lines.get(0));
obj.put("ok", ok);
lines = getZkRawResponse(zkHostPort, "mntr");
validateZkRawResponse(lines, zkHostPort,"mntr");
for (String line : lines) {
String[] parts = line.split("\t");
if (parts.length >= 2) {
obj.put(parts[0], parts[1]);
} else {
String err = String.format("Unexpected line in 'mntr' response from Zookeeper %s: %s", zkHostPort, line);
String err = String.format(Locale.ENGLISH, "Unexpected line in 'mntr' response from Zookeeper %s: %s", zkHostPort, line);
log.warn(err);
errors.add(err);
}
}
lines = getZkRawResponse(zkHostPort, "conf");
validateZkRawResponse(lines, zkHostPort,"conf");
for (String line : lines) {
String[] parts = line.split("=");
if (parts.length >= 2) {
obj.put(parts[0], parts[1]);
} else if (!line.startsWith("membership:")) {
String err = String.format("Unexpected line in 'conf' response from Zookeeper %s: %s", zkHostPort, line);
String err = String.format(Locale.ENGLISH, "Unexpected line in 'conf' response from Zookeeper %s: %s", zkHostPort, line);
log.warn(err);
errors.add(err);
}
@ -219,7 +224,7 @@ public final class ZookeeperStatusHandler extends RequestHandlerBase {
* @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) {
protected List<String> getZkRawResponse(String zkHostPort, String fourLetterWordCommand) {
String[] hostPort = zkHostPort.split(":");
String host = hostPort[0];
int port = ZOOKEEPER_DEFAULT_PORT;
@ -235,18 +240,30 @@ public final class ZookeeperStatusHandler extends RequestHandlerBase {
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);
}
if (response.size() == 1 && response.get(0).contains("not in the whitelist")) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Could not execute " + fourLetterWordCommand +
" towards ZK host " + zkHostPort + ". Add this line to the 'zoo.cfg' " +
"configuration file on each zookeeper node: '4lw.commands.whitelist=mntr,conf,ruok'. See also chapter " +
"'Setting Up an External ZooKeeper Ensemble' in the Solr Reference Guide.");
}
return response;
} catch (IOException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Failed talking to Zookeeper " + zkHostPort, e);
}
}
/**
* Takes the raw response lines returned by {@link #getZkRawResponse(String, String)} and runs some validations
* @param response the lines
* @param zkHostPort the host
* @param fourLetterWordCommand the 4lw command
* @return true if validation succeeds
* @throws SolrException if validation fails
*/
protected boolean validateZkRawResponse(List<String> response, String zkHostPort, String fourLetterWordCommand) {
if (response == null || response.isEmpty()) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Empty response from Zookeeper " + zkHostPort);
}
if (response.size() == 1 && response.get(0).contains("not in the whitelist")) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Could not execute " + fourLetterWordCommand +
" towards ZK host " + zkHostPort + ". Add this line to the 'zoo.cfg' " +
"configuration file on each zookeeper node: '4lw.commands.whitelist=mntr,conf,ruok'. See also chapter " +
"'Setting Up an External ZooKeeper Ensemble' in the Solr Reference Guide.");
}
return true;
}
}

View File

@ -20,6 +20,7 @@ package org.apache.solr.handler.admin;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
@ -34,13 +35,22 @@ 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.apache.solr.common.util.SuppressForbidden;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Answers;
import org.mockito.ArgumentMatchers;
import org.noggit.JSONUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class ZookeeperStatusHandlerTest extends SolrCloudTestCase {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@ -87,4 +97,56 @@ public class ZookeeperStatusHandlerTest extends SolrCloudTestCase {
assertTrue(Integer.parseInt((String) details.get("zk_znode_count")) > 50);
solr.close();
}
@Test
public void testEnsembleStatusMock() {
assumeWorkingMockito();
ZookeeperStatusHandler zkStatusHandler = mock(ZookeeperStatusHandler.class);
when(zkStatusHandler.getZkRawResponse("zoo1:2181", "ruok")).thenReturn(Arrays.asList("imok"));
when(zkStatusHandler.getZkRawResponse("zoo1:2181", "mntr")).thenReturn(
Arrays.asList("zk_version\t3.5.5-390fe37ea45dee01bf87dc1c042b5e3dcce88653, built on 05/03/2019 12:07 GMT",
"zk_avg_latency\t1"));
when(zkStatusHandler.getZkRawResponse("zoo1:2181", "conf")).thenReturn(
Arrays.asList("clientPort=2181",
"secureClientPort=-1",
"thisIsUnexpected",
"membership: "));
when(zkStatusHandler.getZkRawResponse("zoo2:2181", "ruok")).thenReturn(Arrays.asList(""));
when(zkStatusHandler.getZkRawResponse("zoo3:2181", "ruok")).thenReturn(Arrays.asList("imok"));
when(zkStatusHandler.getZkRawResponse("zoo3:2181", "mntr")).thenReturn(
Arrays.asList("mntr is not executed because it is not in the whitelist.")); // Actual response from ZK if not whitelisted
when(zkStatusHandler.getZkRawResponse("zoo3:2181", "conf")).thenReturn(
Arrays.asList("clientPort=2181"));
when(zkStatusHandler.getZkStatus(anyString())).thenCallRealMethod();
when(zkStatusHandler.monitorZookeeper(anyString())).thenCallRealMethod();
when(zkStatusHandler.validateZkRawResponse(ArgumentMatchers.any(), any(), any())).thenAnswer(Answers.CALLS_REAL_METHODS);
Map<String, Object> mockStatus = zkStatusHandler.getZkStatus("zoo1:2181,zoo2:2181,zoo3:2181");
String expected = "{\n" +
" \"ensembleSize\":3,\n" +
" \"details\":[\n" +
" {\n" +
" \"zk_version\":\"3.5.5-390fe37ea45dee01bf87dc1c042b5e3dcce88653, built on 05/03/2019 12:07 GMT\",\n" +
" \"zk_avg_latency\":\"1\",\n" +
" \"host\":\"zoo1:2181\",\n" +
" \"clientPort\":\"2181\",\n" +
" \"secureClientPort\":\"-1\",\n" +
" \"ok\":true},\n" +
" {\n" +
" \"host\":\"zoo2:2181\",\n" +
" \"ok\":false},\n" +
" {\n" +
" \"host\":\"zoo3:2181\",\n" +
" \"ok\":false}],\n" +
" \"zkHost\":\"zoo1:2181,zoo2:2181,zoo3:2181\",\n" +
" \"errors\":[\n" +
" \"Unexpected line in 'conf' response from Zookeeper zoo1:2181: thisIsUnexpected\",\n" +
" \"Empty response from Zookeeper zoo2:2181\",\n" +
" \"Could not execute mntr towards ZK host zoo3:2181. Add this line to the 'zoo.cfg' configuration file on each zookeeper node: '4lw.commands.whitelist=mntr,conf,ruok'. See also chapter 'Setting Up an External ZooKeeper Ensemble' in the Solr Reference Guide.\"],\n" +
" \"status\":\"yellow\"}";
assertEquals(expected, JSONUtil.toJSON(mockStatus));
}
}

View File

@ -530,7 +530,7 @@ var zkStatusSubController = function($scope, ZookeeperStatus) {
$scope.initZookeeper = function() {
ZookeeperStatus.monitor({}, function(data) {
$scope.zkState = data.zkStatus;
$scope.mainKeys = ["ok", "clientPort", "zk_server_state", "zk_version",
$scope.mainKeys = ["ok", "clientPort", "secureClientPort", "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",
@ -538,7 +538,14 @@ var zkStatusSubController = function($scope, ZookeeperStatus) {
"tickTime", "maxClientCnxns", "minSessionTimeout", "maxSessionTimeout"];
$scope.ensembleMainKeys = ["serverId", "electionPort", "quorumPort"];
$scope.ensembleDetailKeys = ["peerType", "electionAlg", "initLimit", "syncLimit",
"zk_followers", "zk_synced_followers", "zk_pending_syncs"];
"zk_followers", "zk_synced_followers", "zk_pending_syncs",
"server.1", "server.2", "server.3", "server.4", "server.5"];
$scope.notEmptyRow = function(key) {
for (hostId in $scope.zkState.details) {
if (key in $scope.zkState.details[hostId]) return true;
}
return false;
};
});
};

View File

@ -67,7 +67,7 @@ limitations under the License.
{{host[key]}}
</td>
</tr>
<tr ng-repeat="key in ensembleDetailKeys" ng-show="showDetails && zkState.mode === 'ensemble'">
<tr ng-repeat="key in ensembleDetailKeys | filter: notEmptyRow" ng-show="showDetails && zkState.mode === 'ensemble'">
<td>{{key}}</td>
<td ng-repeat="host in zkState.details">
{{host[key]}}