docs: private AbstractTopology getTopology(StartAction startAction, Map<Integer, HostInfo> hostInfos,
              JoinCoordinator joinCoordinator)
      {
          AbstractTopology topology = null;
          if (startAction == StartAction.JOIN) {
              assert(joinCoordinator != null);
              topology = joinCoordinator.getTopology();
          } else if (startAction.doesRejoin()) {
              topology = TopologyZKUtils.readTopologyFromZK(m_messenger.getZK());
          } else {
              try {
                  return TopologyZKUtils.readTopologyFromZK(m_messenger.getZK(), null);
              } catch (KeeperException.NoNodeException e) {
                  hostLog.debug("Topology doesn't exist yet try to create it");
              } catch (KeeperException | InterruptedException | JSONException e) {
                  VoltDB.crashLocalVoltDB("Unable to read topology from ZK, dying", true, e);
              }
  
              // initial start or recover
              int hostcount = m_clusterSettings.get().hostcount();
              if (hostInfos.size() != (hostcount - m_config.m_missingHostCount)) {
                  VoltDB.crashLocalVoltDB("The total number of live and missing hosts must be the same as the cluster host count", false, null);
              }
              int kfactor = getKFactor();
              if (kfactor == 0 && m_config.m_missingHostCount > 0) {
                  VoltDB.crashLocalVoltDB("A cluster with 0 kfactor can not be started with missing nodes ", false, null);
              }
              if (hostcount <= kfactor) {
                  VoltDB.crashLocalVoltDB("Not enough nodes to ensure K-Safety.", false, null);
              }
              // Missing hosts can't be more than number of partition groups times k-factor
              int partitionGroupCount = m_clusterSettings.get().hostcount() / (kfactor + 1);
              if (m_config.m_missingHostCount > (partitionGroupCount * kfactor)) {
                  VoltDB.crashLocalVoltDB("Too many nodes are missing at startup. This cluster only allow up to "
                          + (partitionGroupCount * kfactor) + " missing hosts.");
              }
  
              //startup or recover a cluster with missing nodes. make up the missing hosts to fool the topology
              //The topology will contain hosts which are marked as missing.The missing hosts will not host any master partitions.
              //At least one partition replica must be on the live hosts (not missing). Otherwise, the cluster will not be started up.
              //LeaderAppointer will ignore these hosts during startup.
              int sph = hostInfos.values().iterator().next().m_localSitesCount;
              int missingHostId = Integer.MAX_VALUE;
              Set<Integer> missingHosts = Sets.newHashSet();
              for (int i = 0; i < m_config.m_missingHostCount; i++) {
                  hostInfos.put(missingHostId, new HostInfo("", AbstractTopology.PLACEMENT_GROUP_DEFAULT, sph, ""));
                  missingHosts.add(missingHostId--);
              }
              int totalSites = sph * hostcount;
              if (totalSites % (kfactor + 1) != 0) {
                  VoltDB.crashLocalVoltDB("Total number of sites is not divisible by the number of partitions.", false, null);
              }
              topology = AbstractTopology.getTopology(hostInfos, missingHosts, kfactor,
                      (m_config.m_restorePlacement && m_config.m_startAction.doesRecover()));
              String err;
              if ((err = topology.validateLayout(m_messenger.getLiveHostIds())) != null) {
                  hostLog.warn("Unable to find optimal placement layout. " + err);
                  hostLog.warn("When using placement groups, follow two rules to get better cluster availability:\n" +
                               "   1. Each placement group must have the same number of nodes, and\n" +
                               "   2. The number of partition replicas (kfactor + 1) must be a multiple of the number of placement groups.");
              }
              if (topology.hasMissingPartitions()) {
                  VoltDB.crashLocalVoltDB("Some partitions are missing in the topology", false, null);
              }
              if (m_config.m_restorePlacement && m_config.m_startAction.doesRecover() && topology.version > 1) {
                  consoleLog.info("Partition placement has been restored.");
              }
              topology = TopologyZKUtils.registerTopologyToZK(m_messenger.getZK(), topology);
          }
          return topology;
      }