Julian Chandra Sutadi created ZOOKEEPER-5110:
------------------------------------------------

             Summary: AdminServer "monitor" command fails with HTTP 500 
(NullPointerException) while the peer is not serving
                 Key: ZOOKEEPER-5110
                 URL: https://issues.apache.org/jira/browse/ZOOKEEPER-5110
             Project: ZooKeeper
          Issue Type: Bug
            Reporter: Julian Chandra Sutadi


ZOOKEEPER-3353 made the "monitor" admin command server-optional, so that it can 
run even when no ZooKeeperServer exists (e.g. while a peer is in leader 
election), and guarded the server-specific metrics with a null check:

{code:java}
// ZOOKEEPER-3353: MonitorCommand.run(...)
response.put("version", Version.getFullVersion());
...
if (zkServer != null) {
    // server-specific metrics
    ...
}
{code}

The refactor done by ZOOKEEPER-3366 kept the monitor admin command 
server-optional (i.e. can be run even when no ZooKeeperServer exists). However, 
now it invokes the zkServer.dumpMonitorValues(...) without checking if 
(zkServer != null), which was the guard added by ZOOKEEPER-3353.

{code:java}
// Commands.MonitorCommand (current master)
public MonitorCommand() {
    super(Arrays.asList("monitor", "mntr"), false);       // false = 
server-optional
}

@Override
public CommandResponse runGet(ZooKeeperServer zkServer, Map<String, String> 
kwargs) {
    CommandResponse response = initializeResponse();
    zkServer.dumpMonitorValues(response::put);        // NPE when zkServer == 
null
    ServerMetrics.getMetrics().getMetricsProvider().dump(response::put);
    return response;
}
{code}

Because the command is server-optional, Commands.runCommand skips its "not 
currently serving" check and passes a null server to it:

{code:java}
private static CommandResponse runCommand(...) {
    if (command.isServerRequired() && (zkServer == null || 
!zkServer.isRunning())) {    // skipped because command.isServerRequired() is 
false
        // set the status code to 200 to keep the current behavior of existing 
commands
        LOG.warn("This ZooKeeper instance is not currently serving requests for 
command");
        return new CommandResponse(cmdName, "This ZooKeeper instance is not 
currently serving requests", HttpServletResponse.SC_OK);
    }
    ...
    return isGet ? command.runGet(zkServer, kwargs) : command.runPost(zkServer, 
inputStream);
}
{code}

*Steps to reproduce*

1. Configure a zookeeper ensemble
2. Start one server and leave the others stopped. This simulatse a peer in a 
running ensemble after it loses quorum and has no ZooKeeperServer object.
3. Run {{curl http://<host>:8080/commands/monitor}}. It returns HTTP 500:
{noformat}
java.lang.NullPointerException: Cannot invoke 
"org.apache.zookeeper.server.ZooKeeperServer.dumpMonitorValues(java.util.function.BiConsumer)"
 because "zkServer" is null
{noformat}
4. Run {{curl http://<host>:8080/commands/configuration}}, which is a 
server-required command. As a comparison, it returns HTTP 200 with {{"error" : 
"This ZooKeeper instance is not currently serving requests"}}



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to