// partitionReverse the graph down to a subgraph starting from the given root func partitionReverse(g osgraph.Graph, root graph.Node, buildInputEdgeKinds []string) osgraph.Graph { // Filter out all but BuildConfig and ImageStreamTag nodes nodeFn := osgraph.NodesOfKind(buildgraph.BuildConfigNodeKind, imagegraph.ImageStreamTagNodeKind) // Filter out all but BuildInputImage and BuildOutput edges edgeKinds := []string{} edgeKinds = append(edgeKinds, buildInputEdgeKinds...) edgeKinds = append(edgeKinds, buildedges.BuildOutputEdgeKind) edgeFn := osgraph.EdgesOfKind(edgeKinds...) sub := g.Subgraph(nodeFn, edgeFn) // Filter out inbound edges to the IST of interest edgeFn = osgraph.RemoveOutboundEdges([]graph.Node{root}) sub = sub.Subgraph(nodeFn, edgeFn) // Check all paths leading from the root node, collect any // node found in them, and create the desired subgraph desired := []graph.Node{root} paths := path.DijkstraAllPaths(sub) for _, node := range sub.Nodes() { if node == root { continue } path, _, _ := paths.Between(node, root) if len(path) != 0 { desired = append(desired, node) } } return sub.SubgraphWithNodes(desired, osgraph.ExistingDirectEdge) }
// FindCircularBuilds checks all build configs for cycles func FindCircularBuilds(g osgraph.Graph) []osgraph.Marker { // Filter out all but ImageStreamTag and BuildConfig nodes nodeFn := osgraph.NodesOfKind(imagegraph.ImageStreamTagNodeKind, buildgraph.BuildConfigNodeKind) // Filter out all but BuildInputImage and BuildOutput edges edgeFn := osgraph.EdgesOfKind(buildedges.BuildInputImageEdgeKind, buildedges.BuildOutputEdgeKind) // Create desired subgraph sub := g.Subgraph(nodeFn, edgeFn) markers := []osgraph.Marker{} // Check for cycles for _, cycle := range topo.CyclesIn(sub) { nodeNames := []string{} for _, node := range cycle { if resourceStringer, ok := node.(osgraph.ResourceNode); ok { nodeNames = append(nodeNames, resourceStringer.ResourceString()) } } markers = append(markers, osgraph.Marker{ Node: cycle[0], RelatedNodes: cycle, Severity: osgraph.WarningSeverity, Key: CyclicBuildConfigWarning, Message: fmt.Sprintf("Cycle detected in build configurations: %s", strings.Join(nodeNames, " -> ")), }) } return markers }
// 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 }
// subgraphWithoutPrunableImages creates a subgraph from g with prunable image // nodes excluded. func subgraphWithoutPrunableImages(g graph.Graph, prunableImageIDs graph.NodeSet) graph.Graph { return g.Subgraph( func(g graph.Interface, node gonum.Node) bool { return !prunableImageIDs.Has(node.ID()) }, func(g graph.Interface, head, tail gonum.Node, edgeKinds util.StringSet) bool { if prunableImageIDs.Has(head.ID()) { return false } if prunableImageIDs.Has(tail.ID()) { return false } return true }, ) }
// subgraphWithoutPrunableImages creates a subgraph from g with prunable image // nodes excluded. func subgraphWithoutPrunableImages(g graph.Graph, prunableImageIDs graph.NodeSet) graph.Graph { return g.Subgraph( func(g graph.Interface, node gonum.Node) bool { return !prunableImageIDs.Has(node.ID()) }, func(g graph.Interface, from, to gonum.Node, edgeKinds sets.String) bool { if prunableImageIDs.Has(from.ID()) { return false } if prunableImageIDs.Has(to.ID()) { return false } return true }, ) }
// ServiceAndDeploymentGroups breaks the provided graph of API relationships into ServiceGroup objects, // ordered consistently. Groups are organized so that overlapping Services and DeploymentConfigs are // part of the same group, Deployment Configs are each in their own group, and then BuildConfigs are // part of the last service group. func ServiceAndDeploymentGroups(g osgraph.Graph) []ServiceGroup { deploys, covered := DeploymentPipelines(g) other := g.Subgraph(UncoveredDeploymentFlowNodes(covered), UncoveredDeploymentFlowEdges(covered)) components := search.Tarjan(other) serviceGroups := []ServiceGroup{} for _, c := range components { group := ServiceGroup{} matches := osgraph.NodesByKind(other, c, kubegraph.ServiceNodeKind, deploygraph.DeploymentConfigNodeKind) svcs, dcs, _ := matches[0], matches[1], matches[2] for _, n := range svcs { coveredDCs := []*deploygraph.DeploymentConfigNode{} coveredRCs := []*kubegraph.ReplicationControllerNode{} coveredPods := []*kubegraph.PodNode{} for _, neighbor := range other.Neighbors(n) { switch other.EdgeKind(g.EdgeBetween(neighbor, n)) { case kubeedges.ExposedThroughServiceEdgeKind: containerNode := osgraph.GetTopLevelContainerNode(g, neighbor) switch castCoveredNode := containerNode.(type) { case *deploygraph.DeploymentConfigNode: coveredDCs = append(coveredDCs, castCoveredNode) case *kubegraph.ReplicationControllerNode: coveredRCs = append(coveredRCs, castCoveredNode) case *kubegraph.PodNode: coveredPods = append(coveredPods, castCoveredNode) } } } group.Services = append(group.Services, ServiceReference{ Service: n.(*kubegraph.ServiceNode), CoveredDCs: coveredDCs, CoveredRCs: coveredRCs, CoveredPods: coveredPods, }) } sort.Sort(SortedServiceReferences(group.Services)) for _, n := range dcs { d := n.(*deploygraph.DeploymentConfigNode) group.Deployments = append(group.Deployments, DeploymentFlow{ Deployment: d, Images: deploys[d], }) } sort.Sort(SortedDeploymentPipelines(group.Deployments)) if len(dcs) == 0 || len(svcs) == 0 { unknown := g.SubgraphWithNodes(c, osgraph.ExistingDirectEdge) for _, n := range unknown.NodeList() { g.PredecessorEdges(n, osgraph.AddGraphEdgesTo(unknown), buildedges.BuildOutputEdgeKind) } unknown = unknown.EdgeSubgraph(osgraph.ReverseGraphEdge) for _, n := range unknown.RootNodes() { if flow, ok := ImagePipelineFromNode(unknown, n, make(osgraph.NodeSet)); ok { group.Builds = append(group.Builds, flow) } } } sort.Sort(SortedImagePipelines(group.Builds)) serviceGroups = append(serviceGroups, group) } sort.Sort(SortedServiceGroups(serviceGroups)) return serviceGroups }