// FindOverlappingHPAs scans the graph in search of HorizontalPodAutoscalers that are attempting to scale the same set of pods. // This can occur in two ways: // - 1. label selectors for two ReplicationControllers/DeploymentConfigs/etc overlap // - 2. multiple HorizontalPodAutoscalers are attempting to scale the same ReplicationController/DeploymentConfig/etc // Case 1 is handled by deconflicting the area of influence of ReplicationControllers/DeploymentConfigs/etc, and therefore we // can assume that it will be handled before this step. Therefore, we are only concerned with finding HPAs that are trying to // scale the same resources. // // The algorithm that is used to implement this check is described as follows: // - create a sub-graph containing only HPA nodes and other nodes that can be scaled, as well as any scaling edges or other // edges used to connect between objects that can be scaled // - for every resulting edge in the new sub-graph, create an edge in the reverse direction // - find the shortest paths between all HPA nodes in the graph // - shortest paths connecting two horizontal pod autoscalers are used to create markers for the graph func FindOverlappingHPAs(graph osgraph.Graph, namer osgraph.Namer) []osgraph.Marker { markers := []osgraph.Marker{} nodeFilter := osgraph.NodesOfKind( kubenodes.HorizontalPodAutoscalerNodeKind, kubenodes.ReplicationControllerNodeKind, deploynodes.DeploymentConfigNodeKind, ) edgeFilter := osgraph.EdgesOfKind( kubegraph.ScalingEdgeKind, deploygraph.DeploymentEdgeKind, kubeedges.ManagedByControllerEdgeKind, ) hpaSubGraph := graph.Subgraph(nodeFilter, edgeFilter) for _, edge := range hpaSubGraph.Edges() { osgraph.AddReversedEdge(hpaSubGraph, edge.From(), edge.To(), sets.NewString()) } hpaNodes := hpaSubGraph.NodesByKind(kubenodes.HorizontalPodAutoscalerNodeKind) for _, firstHPA := range hpaNodes { // we can use Dijkstra's algorithm as we know we do not have any negative edge weights shortestPaths := path.DijkstraFrom(firstHPA, hpaSubGraph) for _, secondHPA := range hpaNodes { if firstHPA == secondHPA { continue } shortestPath, _ := shortestPaths.To(secondHPA) if shortestPath == nil { // if two HPAs have no path between them, no error exists continue } markers = append(markers, osgraph.Marker{ Node: firstHPA, Severity: osgraph.WarningSeverity, RelatedNodes: shortestPath[1:], Key: HPAOverlappingScaleRefWarning, Message: fmt.Sprintf("%s and %s overlap because they both attempt to scale %s", namer.ResourceName(firstHPA), namer.ResourceName(secondHPA), nameList(shortestPath[1:len(shortestPath)-1], namer)), }) } } return markers }
func TestDijkstraFrom(t *testing.T) { for _, test := range shortestPathTests { g := test.g() for _, e := range test.edges { g.SetEdge(e, e.Cost) } var ( pt path.Shortest panicked bool ) func() { defer func() { panicked = recover() != nil }() pt = path.DijkstraFrom(test.query.From(), g.(graph.Graph)) }() if panicked || test.negative { if !test.negative { t.Errorf("%q: unexpected panic", test.name) } if !panicked { t.Errorf("%q: expected panic for negative edge weight", test.name) } continue } if pt.From().ID() != test.query.From().ID() { t.Fatalf("%q: unexpected from node ID: got:%d want:%d", pt.From().ID(), test.query.From().ID()) } p, weight := pt.To(test.query.To()) if weight != test.weight { t.Errorf("%q: unexpected weight from Between: got:%f want:%f", test.name, weight, test.weight) } if weight := pt.WeightTo(test.query.To()); weight != test.weight { t.Errorf("%q: unexpected weight from Weight: got:%f want:%f", test.name, weight, test.weight) } var got []int for _, n := range p { got = append(got, n.ID()) } ok := len(got) == 0 && len(test.want) == 0 for _, sp := range test.want { if reflect.DeepEqual(got, sp) { ok = true break } } if !ok { t.Errorf("%q: unexpected shortest path:\ngot: %v\nwant from:%v", test.name, p, test.want) } np, weight := pt.To(test.none.To()) if pt.From().ID() == test.none.From().ID() && (np != nil || !math.IsInf(weight, 1)) { t.Errorf("%q: unexpected path:\ngot: path=%v weight=%f\nwant:path=<nil> weight=+Inf", test.name, np, weight) } } }