private static Optional<Result> checkZeroEntriesStoredOnContentNode(NodeDataMetrics metrics, int nodeIndex) {
        if (metrics.entries.isEmpty() || metrics.entries.get().getLast() == null) {
            // To allow for rolling upgrades in clusters with content node versions that do not report
            // an entry count, defer to legacy bucket count check if the entry metric can't be found.
            return Optional.empty();
        }
        if (metrics.docs.isEmpty() || metrics.docs.get().getLast() == null) {
            log.log(Level.WARNING, "Host info inconsistency: storage node %d reports entry count but not document count".formatted(nodeIndex));
            return Optional.of(disallow("The storage node host info reports stored entry count, but not document count"));
        }
        long lastEntries = metrics.entries.get().getLast();
        long lastDocs    = metrics.docs.get().getLast();
        if (lastEntries != 0) {
            long buckets    = metrics.buckets.map(Metrics.Value::getLast).orElse(-1L);
            long tombstones = lastEntries - lastDocs; // docs are a subset of entries, so |docs| <= |entries|
            return Optional.of(disallow("The storage node stores %d documents and %d tombstones across %d buckets".formatted(lastDocs, tombstones, buckets)));
        }
        // At this point we believe we have zero entries. Cross-check with visible doc count; it should
        // always be present when an entry count of zero is present and transitively always be zero.
        if (lastDocs != 0) {
            log.log(Level.WARNING, "Host info inconsistency: storage node %d reports 0 entries, but %d documents".formatted(nodeIndex, lastDocs));
            return Optional.of(disallow("The storage node reports 0 entries, but %d documents".formatted(lastDocs)));
        }
        return Optional.of(allow());
    }
Checks if a node can be upgraded.

@author Haakon Dybdahl
clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeChecker.java