func (r *RestartReconciler) parseClusterNodeStatus(stdout string) map[string]string {
	stateMap := map[string]string{}
	lines := strings.Split(stdout, "\n")
	const ColHeaderCount = 2
	if len(lines) <= ColHeaderCount {
		return stateMap
	}
	for _, line := range lines[ColHeaderCount:] {
		cols := strings.Split(line, "|")
		const ListNodesColCount = 4
		if len(cols) < ListNodesColCount {
			continue
		}
		vnode := strings.Trim(cols[0], " ")
		state := strings.Trim(cols[2], " ")
		stateMap[vnode] = state
	}
	return stateMap
}
parseClusterNodeStatus will parse the output from a AT -t list_allnodes call
package controllers
import (
	"context"
	"fmt"
	"regexp"
	"strings"
	"time"
	"github.com/go-logr/logr"
	vapi "github.com/vertica/vertica-kubernetes/api/v1beta1"
	"github.com/vertica/vertica-kubernetes/pkg/cmds"
	"github.com/vertica/vertica-kubernetes/pkg/events"
	"github.com/vertica/vertica-kubernetes/pkg/names"
	"github.com/vertica/vertica-kubernetes/pkg/paths"
	"github.com/vertica/vertica-kubernetes/pkg/status"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	ctrl "sigs.k8s.io/controller-runtime"
)
const (
	RequeueWaitTimeInSeconds = 10
	AdminToolsMapFile = "/opt/vertica/config/ipMap.txt"
	StateUp = "UP"
)
type verticaIPLookup map[string]string
type RestartReconciler struct {
	VRec    *VerticaDBReconciler
	Log     logr.Logger
	Vdb     *vapi.VerticaDB  
	PRunner cmds.PodRunner
	PFacts  *PodFacts
	ATPod   types.NamespacedName  
}
func MakeRestartReconciler(vdbrecon *VerticaDBReconciler, log logr.Logger,
	vdb *vapi.VerticaDB, prunner cmds.PodRunner, pfacts *PodFacts) ReconcileActor {
	return &RestartReconciler{VRec: vdbrecon, Log: log, Vdb: vdb, PRunner: prunner, PFacts: pfacts}
}
func (r *RestartReconciler) Reconcile(ctx context.Context, req *ctrl.Request) (ctrl.Result, error) {
	if !r.Vdb.Spec.AutoRestartVertica {
		err := status.UpdateCondition(ctx, r.VRec.Client, r.Vdb,
			vapi.VerticaDBCondition{Type: vapi.AutoRestartVertica, Status: corev1.ConditionFalse},
		)
		return ctrl.Result{}, err
	}
	err := status.UpdateCondition(ctx, r.VRec.Client, r.Vdb,
		vapi.VerticaDBCondition{Type: vapi.AutoRestartVertica, Status: corev1.ConditionTrue},
	)
	if err != nil {
		return ctrl.Result{}, err
	}
	if err := r.PFacts.Collect(ctx, r.Vdb); err != nil {
		return ctrl.Result{}, err
	}
	if r.PFacts.getUpNodeCount() == 0 && r.Vdb.Spec.InitPolicy != vapi.CommunalInitPolicyScheduleOnly {
		return r.reconcileCluster(ctx)
	}
	return r.reconcileNodes(ctx)
}
func (r *RestartReconciler) reconcileCluster(ctx context.Context) (ctrl.Result, error) {
	if r.PFacts.areAllPodsRunningAndZeroInstalled() {
		r.Log.Info("All pods are running and none of them have an installation.  Nothing to restart.")
		return ctrl.Result{}, nil
	}
	if r.PFacts.countRunningAndInstalled() == 0 {
		r.Log.Info("Waiting for pods to come online that may need a Vertica restart")
		return ctrl.Result{Requeue: true}, nil
	}
	if ok := r.setATPod(); !ok {
		r.Log.Info("No pod found to run admintools from. Requeue reconciliation.")
		return ctrl.Result{Requeue: true}, nil
	}
	if res, err := r.reipNodes(ctx, r.PFacts.findReIPPods(false)); err != nil || res.Requeue {
		return res, err
	}
	dbDoesNotExist := !r.PFacts.doesDBExist().IsTrue()
	if !dbDoesNotExist {
		if upNodes, err := r.anyUpNodesInClusterState(ctx); err != nil || upNodes {
			return ctrl.Result{Requeue: upNodes}, err
		}
	}
	downPods := r.PFacts.findRestartablePods()
	if err := r.killOldProcesses(ctx, downPods); err != nil {
		return ctrl.Result{}, err
	}
	if dbDoesNotExist {
		return ctrl.Result{}, nil
	}
	return r.restartCluster(ctx)
}
func (r *RestartReconciler) reconcileNodes(ctx context.Context) (ctrl.Result, error) {
	downPods := r.PFacts.findRestartablePods()
	if len(downPods) > 0 {
		if ok := r.setATPod(); !ok {
			r.Log.Info("No pod found to run admintools from. Requeue reconciliation.")
			return ctrl.Result{Requeue: true}, nil
		}
		if res, err := r.restartPods(ctx, downPods); res.Requeue || res.RequeueAfter > 0 || err != nil {
			return res, err
		}
	}
	if r.Vdb.Spec.InitPolicy == vapi.CommunalInitPolicyScheduleOnly {
		return ctrl.Result{Requeue: r.shouldRequeueIfPodsNotRunning()}, nil
	}
	reIPPods := r.PFacts.findReIPPods(true)
	if len(reIPPods) > 0 {
		if ok := r.setATPod(); !ok {
			r.Log.Info("No pod found to run admintools from. Requeue reconciliation.")
			return ctrl.Result{Requeue: true}, nil
		}
		if res, err := r.reipNodes(ctx, reIPPods); res.Requeue || err != nil {
			return res, err
		}
	}
	return ctrl.Result{Requeue: r.shouldRequeueIfPodsNotRunning()}, nil
}
func (r *RestartReconciler) restartPods(ctx context.Context, pods []*PodFact) (ctrl.Result, error) {
	downPods, err := r.removePodsWithClusterUpState(ctx, pods)
	if err != nil {
		return ctrl.Result{}, err
	}
	if len(downPods) == 0 {
		return ctrl.Result{Requeue: true}, nil
	}
	vnodeList := genRestartVNodeList(downPods)
	ipList := genRestartIPList(downPods)
	if err := r.killOldProcesses(ctx, downPods); err != nil {
		return ctrl.Result{}, err
	}
	debugDumpAdmintoolsConf(ctx, r.PRunner, r.ATPod)
	cmd := r.genRestartNodeCmd(vnodeList, ipList)
	if stdout, err := r.execRestartPods(ctx, downPods, cmd); err != nil {
		if strings.Contains(stdout, "All nodes in the input are not down, can't restart") {
			return ctrl.Result{Requeue: false, RequeueAfter: time.Second * RequeueWaitTimeInSeconds}, nil
		}
		return ctrl.Result{}, fmt.Errorf("failed to restart pod(s) %w", err)
	}
	debugDumpAdmintoolsConf(ctx, r.PRunner, r.ATPod)
	r.PFacts.Invalidate()
	return ctrl.Result{Requeue: len(pods) > len(downPods)}, nil
}
func (r *RestartReconciler) anyUpNodesInClusterState(ctx context.Context) (bool, error) {
	clusterState, err := r.fetchClusterNodeStatus(ctx)
	if err != nil {
		return false, err
	}
	for _, state := range clusterState {
		if state == StateUp {
			return true, nil
		}
	}
	return false, nil
}
func (r *RestartReconciler) removePodsWithClusterUpState(ctx context.Context, pods []*PodFact) ([]*PodFact, error) {
	clusterState, err := r.fetchClusterNodeStatus(ctx)
	if err != nil {
		return nil, err
	}
	i := 0
	for _, pod := range pods {
		state, ok := clusterState[pod.vnodeName]
		if !ok || state != StateUp {
			pods[i] = pod
			i++
		}
	}
	return pods[:i], nil
}
func (r *RestartReconciler) fetchClusterNodeStatus(ctx context.Context) (map[string]string, error) {
	cmd := []string{
		"-t", "list_allnodes",
	}
	stdout, _, err := r.PRunner.ExecAdmintools(ctx, r.ATPod, names.ServerContainer, cmd...)
	if err != nil {
		return nil, err
	}
	return r.parseClusterNodeStatus(stdout), nil
}