func (p *PodFacts) getUpNodeCount() int {
	var count = 0
	for _, v := range p.Detail {
		if v.upNode {
			count++
		}
	}
	return count
}
getUpNodeCount returns the number of up nodes.
A pod is considered down if it doesn't have a running vertica process.
package controllers
import (
	"context"
	"fmt"
	"regexp"
	"sort"
	"strings"
	vapi "github.com/vertica/vertica-kubernetes/api/v1beta1"
	"github.com/vertica/vertica-kubernetes/pkg/cmds"
	"github.com/vertica/vertica-kubernetes/pkg/names"
	"github.com/vertica/vertica-kubernetes/pkg/paths"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"yunion.io/x/pkg/tristate"
)
type PodFact struct {
	name types.NamespacedName
	dnsName string
	podIP string
	subcluster string
	exists bool
	isPodRunning bool
	managedByParent bool
	isInstalled tristate.TriState
	hasStaleAdmintoolsConf bool
	dbExists tristate.TriState
	upNode bool
	vnodeName string
	compat21NodeName string
	eulaAccepted tristate.TriState
	configLogrotateExists bool
	configLogrotateWritable bool
	configShareExists bool
}
type PodFactDetail map[types.NamespacedName]*PodFact
type PodFacts struct {
	client.Client
	PRunner        cmds.PodRunner
	Detail         PodFactDetail
	NeedCollection bool
}
func MakePodFacts(cli client.Client, prunner cmds.PodRunner) PodFacts {
	return PodFacts{Client: cli, PRunner: prunner, NeedCollection: true, Detail: make(PodFactDetail)}
}
func (p *PodFacts) Collect(ctx context.Context, vdb *vapi.VerticaDB) error {
	if !p.NeedCollection {
		return nil
	}
	p.Detail = make(PodFactDetail)  
	finder := MakeSubclusterFinder(p.Client, vdb)
	subclusters, err := finder.FindSubclusters(ctx, FindAll)
	if err != nil {
		return nil
	}
	for i := range subclusters {
		if err := p.collectSubcluster(ctx, vdb, subclusters[i]); err != nil {
			return err
		}
	}
	p.NeedCollection = false
	return nil
}
func (p *PodFacts) Invalidate() {
	p.NeedCollection = true
}
func (p *PodFacts) collectSubcluster(ctx context.Context, vdb *vapi.VerticaDB, sc *vapi.Subcluster) error {
	sts := &appsv1.StatefulSet{}
	maxStsSize := sc.Size
	if err := p.Client.Get(ctx, names.GenStsName(vdb, sc), sts); err != nil {
		if !errors.IsNotFound(err) {
			return fmt.Errorf("could not fetch statefulset for pod fact collection %s %w", sc.Name, err)
		}
	} else {
		if *sts.Spec.Replicas > maxStsSize {
			maxStsSize = *sts.Spec.Replicas
		}
	}
	for i := int32(0); i < maxStsSize; i++ {
		if err := p.collectPodByStsIndex(ctx, vdb, sc, sts, i); err != nil {
			return err
		}
	}
	return nil
}
func (p *PodFacts) collectPodByStsIndex(ctx context.Context, vdb *vapi.VerticaDB, sc *vapi.Subcluster,
	sts *appsv1.StatefulSet, podIndex int32) error {
	pf := PodFact{
		name:       names.GenPodName(vdb, sc, podIndex),
		subcluster: sc.Name,
	}
	pod := &corev1.Pod{}
	if err := p.Client.Get(ctx, pf.name, pod); errors.IsNotFound(err) {
		p.Detail[pf.name] = &pf
		return nil
	} else if err != nil {
		return err
	}
	pf.exists = true  
	if sts.Spec.Replicas != nil {
		pf.managedByParent = podIndex < *sts.Spec.Replicas
	}
	pf.isPodRunning = pod.Status.Phase == corev1.PodRunning
	pf.dnsName = pod.Spec.Hostname + "." + pod.Spec.Subdomain
	pf.podIP = pod.Status.PodIP
	fns := []func(ctx context.Context, vdb *vapi.VerticaDB, pf *PodFact) error{
		p.checkIsInstalled,
		p.checkIsDBCreated,
		p.checkIfNodeIsUp,
		p.checkEulaAcceptance,
		p.checkLogrotateExists,
		p.checkIsLogrotateWritable,
		p.checkThatConfigShareExists,
	}
	for _, fn := range fns {
		if err := fn(ctx, vdb, &pf); err != nil {
			return err
		}
	}
	p.Detail[pf.name] = &pf
	return nil
}
func (p *PodFacts) checkIsInstalled(ctx context.Context, vdb *vapi.VerticaDB, pf *PodFact) error {
	if !pf.isPodRunning {
		pf.isInstalled = tristate.None
		return nil
	}
	if vdb.Spec.InitPolicy == vapi.CommunalInitPolicyScheduleOnly {
		if _, _, err := p.PRunner.ExecInPod(ctx, pf.name, names.ServerContainer, "test", "-f", paths.AdminToolsConf); err != nil {
			pf.isInstalled = tristate.False
		} else {
			pf.isInstalled = tristate.True
		}
		pf.compat21NodeName = ""
		return nil
	}
	fn := vdb.GenInstallerIndicatorFileName()
	if stdout, stderr, err := p.PRunner.ExecInPod(ctx, pf.name, names.ServerContainer, "cat", fn); err != nil {
		if !strings.Contains(stderr, "cat: "+fn+": No such file or directory") {
			return err
		}
		pf.isInstalled = tristate.False
		cmd := []string{"ls", paths.AdminToolsConf}
		if _, stderr, err := p.PRunner.ExecInPod(ctx, pf.name, names.ServerContainer, cmd...); err != nil {
			if !strings.Contains(stderr, "No such file or directory") {
				return err
			}
			pf.hasStaleAdmintoolsConf = false
		} else {
			pf.hasStaleAdmintoolsConf = true
		}
	} else {
		pf.isInstalled = tristate.True
		pf.compat21NodeName = strings.TrimSuffix(stdout, "\n")
	}
	return nil
}
func (p *PodFacts) checkEulaAcceptance(ctx context.Context, vdb *vapi.VerticaDB, pf *PodFact) error {
	if pf.isPodRunning {
		if _, stderr, err := p.PRunner.ExecInPod(ctx, pf.name, names.ServerContainer, "cat", paths.EulaAcceptanceFile); err != nil {
			if !strings.Contains(stderr, fmt.Sprintf("cat: %s: No such file or directory", paths.EulaAcceptanceFile)) {
				return err
			}
			pf.eulaAccepted = tristate.False
		} else {
			pf.eulaAccepted = tristate.True
		}
	}
	return nil
}
func (p *PodFacts) checkLogrotateExists(ctx context.Context, vdb *vapi.VerticaDB, pf *PodFact) error {
	if pf.isPodRunning {
		if _, _, err := p.PRunner.ExecInPod(ctx, pf.name, names.ServerContainer, "test", "-d", paths.ConfigLogrotatePath); err == nil {
			pf.configLogrotateExists = true
		}
	}
	return nil
}
func (p *PodFacts) checkIsLogrotateWritable(ctx context.Context, vdb *vapi.VerticaDB, pf *PodFact) error {
	if pf.isPodRunning {
		if _, _, err := p.PRunner.ExecInPod(ctx, pf.name, names.ServerContainer, "test", "-w", paths.ConfigLogrotatePath); err == nil {
			pf.configLogrotateWritable = true
		}
	}
	return nil
}
func (p *PodFacts) checkThatConfigShareExists(ctx context.Context, vdb *vapi.VerticaDB, pf *PodFact) error {
	if pf.isPodRunning {
		if _, _, err := p.PRunner.ExecInPod(ctx, pf.name, names.ServerContainer, "test", "-d", paths.ConfigSharePath); err == nil {
			pf.configShareExists = true
		}
	}
	return nil
}
func (p *PodFacts) checkIsDBCreated(ctx context.Context, vdb *vapi.VerticaDB, pf *PodFact) error {
	if !pf.isPodRunning {
		pf.dbExists = tristate.None
		return nil
	}
	cmd := []string{
		"bash",
		"-c",
		fmt.Sprintf("ls -d %s/v_%s_node????_data", vdb.GetDBDataPath(), vdb.Spec.DBName),
	}
	if stdout, stderr, err := p.PRunner.ExecInPod(ctx, pf.name, names.ServerContainer, cmd...); err != nil {
		if !strings.Contains(stderr, "No such file or directory") {
			return err
		}
		pf.dbExists = tristate.False
	} else {
		pf.dbExists = tristate.True
		pf.vnodeName = parseVerticaNodeName(stdout)
	}
	return nil
}
func (p *PodFacts) checkIfNodeIsUp(ctx context.Context, vdb *vapi.VerticaDB, pf *PodFact) error {
	if pf.dbExists.IsFalse() || !pf.isPodRunning {
		pf.upNode = false
		return nil
	}
	cmd := []string{
		"-c",
		"select 1",
	}
	if _, stderr, err := p.PRunner.ExecVSQL(ctx, pf.name, names.ServerContainer, cmd...); err != nil {
		if !strings.Contains(stderr, "vsql: could not connect to server:") {
			return err
		}
		pf.upNode = false
	} else {
		pf.upNode = true
	}
	return nil
}
func parseVerticaNodeName(stdout string) string {
	re := regexp.MustCompile(`(v_.+_node\d+)_data`)
	match := re.FindAllStringSubmatch(stdout, 1)
	if len(match) > 0 && len(match[0]) > 0 {
		return match[0][1]
	}
	return ""
}
func (p *PodFacts) doesDBExist() tristate.TriState {
	returnOnFail := tristate.False
	for _, v := range p.Detail {
		if v.dbExists.IsTrue() && v.isPodRunning {
			return tristate.True
		} else if v.dbExists.IsNone() || !v.isPodRunning {
			returnOnFail = tristate.None
		}
	}
	return returnOnFail
}
func (p *PodFacts) anyPodsMissingDB(scName string) tristate.TriState {
	returnOnFail := tristate.False
	for _, v := range p.Detail {
		if v.subcluster != scName {
			continue
		}
		if v.dbExists.IsFalse() && v.isPodRunning {
			return tristate.True
		} else if v.dbExists.IsNone() {
			returnOnFail = tristate.None
		}
	}
	return returnOnFail
}
func (p *PodFacts) findPodsWithMissingDB(scName string) []*PodFact {
	hostList := []*PodFact{}
	for _, v := range p.Detail {
		if v.subcluster != scName {
			continue
		}
		if v.dbExists.IsFalse() && v.isPodRunning {
			hostList = append(hostList, v)
		}
	}
	sort.Slice(hostList, func(i, j int) bool {
		return hostList[i].dnsName < hostList[j].dnsName
	})
	return hostList
}
func (p *PodFacts) findPodToRunVsql() (*PodFact, bool) {
	for _, v := range p.Detail {
		if v.upNode {
			return v, true
		}
	}
	return &PodFact{}, false
}
func (p *PodFacts) findPodToRunAdmintools() (*PodFact, bool) {
	for _, v := range p.Detail {
		if v.upNode {
			return v, true
		}
	}
	for _, v := range p.Detail {
		if v.isInstalled.IsTrue() && v.isPodRunning {
			return v, true
		}
	}
	return &PodFact{}, false
}
func (p *PodFacts) findRunningPod() (*PodFact, bool) {
	for _, v := range p.Detail {
		if v.isPodRunning {
			return v, true
		}
	}
	return &PodFact{}, false
}
func (p *PodFacts) findRestartablePods() []*PodFact {
	return p.filterPods(func(v *PodFact) bool {
		return !v.upNode && v.dbExists.IsTrue() && v.isPodRunning
	})
}
func (p *PodFacts) findInstalledPods() []*PodFact {
	return p.filterPods((func(v *PodFact) bool {
		return v.isInstalled.IsTrue() && v.isPodRunning
	}))
}
func (p *PodFacts) findReIPPods(onlyPodsWithoutDBs bool) []*PodFact {
	return p.filterPods(func(pod *PodFact) bool {
		if !pod.exists || pod.isInstalled.IsFalse() {
			return false
		}
		if onlyPodsWithoutDBs && pod.dbExists.IsTrue() {
			return false
		}
		return true
	})
}
func (p *PodFacts) filterPods(filterFunc func(p *PodFact) bool) []*PodFact {
	pods := []*PodFact{}
	for _, v := range p.Detail {
		if filterFunc(v) {
			pods = append(pods, v)
		}
	}
	return pods
}
func (p *PodFacts) areAllPodsRunningAndZeroInstalled() bool {
	for _, v := range p.Detail {
		if ((!v.exists || !v.isPodRunning) && v.managedByParent) || v.isInstalled.IsTrue() {
			return false
		}
	}
	return true
}
func (p *PodFacts) countPods(countFunc func(p *PodFact) int) int {
	count := 0
	for _, v := range p.Detail {
		count += countFunc(v)
	}
	return count
}
func (p *PodFacts) countRunningAndInstalled() int {
	return p.countPods(func(v *PodFact) int {
		if v.isPodRunning && v.isInstalled.IsTrue() {
			return 1
		}
		return 0
	})
}
func (p *PodFacts) countNotRunning() int {
	return p.countPods(func(v *PodFact) int {
		if !v.isPodRunning && v.managedByParent {
			return 1
		}
		return 0
	})
}