docs: private SnapshotInfo getRestorePlan() throws Exception {
          // Wait for ZK to publish the snapshot fragment/info structures.
          List<String> children = waitOnVoltZK_restore();
  
          // If not recovering, nothing to do.
          if (m_action.doesRequireEmptyDirectories()) {
              return null;
          }
  
          Map<String, Set<SnapshotInfo>> snapshotFragments = new HashMap<String, Set<SnapshotInfo>>();
          Long clStartTxnId = deserializeRestoreInformation(children, snapshotFragments);
  
          // If command log has no snapshot requirement clear the fragment set directly
          if (clStartTxnId != null && clStartTxnId == Long.MIN_VALUE) {
              snapshotFragments.clear();
          }
          if (LOG.isDebugEnabled()) {
              LOG.debug("There are " + snapshotFragments.size() + " restore candidate snapshots available in the cluster");
          }
  
          // Find the last complete snapshot and use it
          HashMap<String, Map<String, Set<Integer>>> snapshotTablePartitions =
              new HashMap<String, Map<String,Set<Integer>>>();
          Iterator<Entry<String, Set<SnapshotInfo>>> it = snapshotFragments.entrySet().iterator();
          SnapshotInfo newest = null;
          while (it.hasNext()) {
              Entry<String, Set<SnapshotInfo>> e = it.next();
              Set<String> fileTables = new HashSet<String>();
              Set<String> digestTables = null;
              String nonce = e.getKey();
              Map<String, Set<Integer>> tablePartitions = snapshotTablePartitions.get(nonce);
              if (tablePartitions == null) {
                  tablePartitions = new HashMap<String, Set<Integer>>();
                  snapshotTablePartitions.put(nonce, tablePartitions);
              }
  
              int totalPartitions = -1;
              boolean inconsistent = false;
              Set<SnapshotInfo> fragments = e.getValue();
              for (SnapshotInfo s : fragments) {
                  if (LOG.isDebugEnabled()) {
                      LOG.debug("SnapshotInfo " + s.nonce + " claims digest tables: " + s.digestTables);
                      LOG.debug("SnapshotInfo " + s.nonce + " claims files for tables: " + s.fileTables);
                  }
                  if (digestTables == null) {
                      digestTables = new HashSet<String>(s.digestTables);
                  }
                  else if (!digestTables.equals(s.digestTables)) {
                      m_snapshotErrLogStr.append("\nRejected snapshot ")
                                      .append(s.nonce)
                                      .append(" due to disagreement in digest table list.  Got ")
                                      .append(s.digestTables)
                                      .append(", expecting ")
                                      .append(digestTables);
                      inconsistent = true;
                      break;
                  }
                  fileTables.addAll(s.fileTables);
                  if (totalPartitions == -1) {
                      totalPartitions = s.partitionCount;
                  } else if (totalPartitions != s.partitionCount) {
                      m_snapshotErrLogStr.append("\nRejected snapshot ")
                                      .append(s.nonce)
                                      .append(" due to partition count mismatch. Got ")
                                      .append(s.partitionCount)
                                      .append(", expecting ")
                                      .append(totalPartitions);
                      inconsistent = true;
                      break;
                  }
  
                  for (Entry<String, Set<Integer>> entry : s.partitions.entrySet()) {
                      Set<Integer> partitions = tablePartitions.get(entry.getKey());
                      if (partitions == null) {
                          tablePartitions.put(entry.getKey(), entry.getValue());
                      } else {
                          partitions.addAll(entry.getValue());
                      }
                  }
  
                  if (inconsistent) {
                      break;
                  }
              }
  
              // Check that we have files for all the tables listed in the digest
              if (!fileTables.equals(digestTables)) {
                  m_snapshotErrLogStr.append("\nRejected snapshot ")
                      .append(nonce)
                      .append(" because there were not table files for all tables in the digest.  Expected ")
                      .append(digestTables)
                      .append(", but only found table data for ")
                      .append(fileTables);
                  inconsistent = true;
              }
  
              // Check if we have all the partitions
              for (Set<Integer> partitions : tablePartitions.values()) {
                  if (partitions.size() != totalPartitions) {
                      m_snapshotErrLogStr.append("\nRejected snapshot ")
                                      .append(nonce)
                                      .append(" due to missing partitions. Got ")
                                      .append(partitions.size())
                                      .append(", expecting ")
                                      .append(totalPartitions);
                      inconsistent = true;
                  }
              }
  
              if (inconsistent) {
                  it.remove();
              }
              else {
                  SnapshotInfo current = consolidateSnapshotInfos(fragments);
                  if (newest == null || current.isNewerThan(newest)) {
                      newest = current;
                  }
              }
          }
  
          // If we have a command log and it requires a snapshot but no snapshot
          // fragments were found, simply bail (or, if we didn't find a viable
          // snapshot from which to recover)
          // Reluctant to muck with the first predicate here, even though
          // the newest == null should cover all of the 'no viable snapshot' possiblities
          if ((clStartTxnId != null && clStartTxnId != Long.MIN_VALUE &&
              snapshotFragments.size() == 0) || newest == null) {
              if (m_snapshotErrLogStr.length() > 0) {
                  LOG.error(m_snapshotErrLogStr.toString());
              }
              throw new RuntimeException("No viable snapshots to restore");
          }
  
          if (snapshotFragments.isEmpty()) {
              return null;
          } else {
              return newest;
          }
      }