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  }