github.com/kiali/kiali@v1.84.0/graph/telemetry/istio/appender/dead_node.go (about) 1 package appender 2 3 import ( 4 "github.com/kiali/kiali/graph" 5 "github.com/kiali/kiali/log" 6 ) 7 8 const DeadNodeAppenderName = "deadNode" 9 10 // DeadNodeAppender is responsible for removing from the graph unwanted nodes: 11 // - nodes for which there is no traffic reported and a backing workload that can't be found 12 // (presumably removed from K8S). (kiali-621) 13 // - this includes "unknown" 14 // - service nodes that are not service entries (kiali-1526) or egress handlers, and for which 15 // there is no incoming traffic or outgoing edge 16 // 17 // Name: deadNode 18 type DeadNodeAppender struct { 19 AccessibleNamespaces graph.AccessibleNamespaces 20 } 21 22 // Name implements Appender 23 func (a DeadNodeAppender) Name() string { 24 return DeadNodeAppenderName 25 } 26 27 // IsFinalizer implements Appender 28 func (a DeadNodeAppender) IsFinalizer() bool { 29 return false 30 } 31 32 // AppendGraph implements Appender 33 func (a DeadNodeAppender) AppendGraph(trafficMap graph.TrafficMap, globalInfo *graph.AppenderGlobalInfo, namespaceInfo *graph.AppenderNamespaceInfo) { 34 if len(trafficMap) == 0 { 35 return 36 } 37 38 // Apply dead node removal iteratively until no dead nodes are found. Removal of dead nodes may 39 // alter the graph such that new nodes qualify for dead-ness by being orphaned, lack required 40 // outgoing edges, etc.. so we repeat as needed. 41 // Should never have to execute more times than the number of nodes in the map, so limit to maxTries 42 // to avoid any sort of infinite loop 43 maxTries := len(trafficMap) 44 applyDeadNodes := true 45 for applyDeadNodes && maxTries > 0 { 46 applyDeadNodes = a.applyDeadNodes(trafficMap, globalInfo, namespaceInfo) > 0 47 maxTries-- 48 } 49 if applyDeadNodes { 50 log.Warningf("DeadNodeAppender infinite loop detection! MaxTries=[%v]", maxTries) 51 } 52 } 53 54 func (a DeadNodeAppender) applyDeadNodes(trafficMap graph.TrafficMap, globalInfo *graph.AppenderGlobalInfo, namespaceInfo *graph.AppenderNamespaceInfo) (numRemoved int) { 55 for id, n := range trafficMap { 56 isDead := true 57 58 // a node with traffic is not dead, skip 59 DefaultCase: 60 for _, p := range graph.Protocols { 61 for _, r := range p.NodeRates { 62 if r.IsIn || r.IsOut { 63 if rate, hasRate := n.Metadata[r.Name]; hasRate && rate.(float64) > 0 { 64 isDead = false 65 break DefaultCase 66 } 67 } 68 } 69 } 70 if !isDead { 71 continue 72 } 73 74 switch n.NodeType { 75 case graph.NodeTypeAggregate: 76 // an aggregate node is never dead 77 continue 78 case graph.NodeTypeService: 79 // a service node with outgoing edges is never considered dead 80 if len(n.Edges) > 0 { 81 continue 82 } 83 84 // A service node that is a service entry is never considered dead 85 if _, ok := n.Metadata[graph.IsServiceEntry]; ok { 86 continue 87 } 88 89 // A service node that is an Istio egress cluster is never considered dead 90 if _, ok := n.Metadata[graph.IsEgressCluster]; ok { 91 continue 92 } 93 94 if isDead { 95 delete(trafficMap, id) 96 numRemoved++ 97 } 98 default: 99 // There are some node types that are never associated with backing workloads (such as versionless app nodes). 100 // Nodes of those types are never dead because their workload clearly can't be missing (they don't have workloads). 101 // - note: unknown is not saved by this rule (kiali-2078) - i.e. unknown nodes can be declared dead 102 if n.NodeType != graph.NodeTypeUnknown && !graph.IsOK(n.Workload) { 103 continue 104 } 105 106 // If the potential workload is not unknown, but is inaccessible, we can't actually check for its existence, so we 107 // need to assume it is valid 108 if n.NodeType != graph.NodeTypeUnknown && !a.nodeOK(n) { 109 continue 110 } 111 112 // Remove if backing workload is not defined (always for "unknown"), flag if there are no pods 113 if workload, found := getWorkload(n.Cluster, namespaceInfo.Namespace, n.Workload, globalInfo); !found { 114 delete(trafficMap, id) 115 numRemoved++ 116 } else { 117 if workload.PodCount == 0 { 118 n.Metadata[graph.IsDead] = true 119 } 120 } 121 } 122 } 123 124 // If we removed any nodes we need to remove any edges to them as well... 125 if numRemoved > 0 { 126 for _, n := range trafficMap { 127 goodEdges := []*graph.Edge{} 128 for _, e := range n.Edges { 129 if _, found := trafficMap[e.Dest.ID]; found { 130 goodEdges = append(goodEdges, e) 131 } 132 } 133 n.Edges = goodEdges 134 } 135 } 136 137 return numRemoved 138 } 139 140 // nodeOK returns true if we have access to its workload info 141 func (a *DeadNodeAppender) nodeOK(node *graph.Node) bool { 142 key := graph.GetClusterSensitiveKey(node.Cluster, node.Namespace) 143 _, ok := a.AccessibleNamespaces[key] 144 return ok 145 }