// addImageStreamsToGraph adds all the streams to the graph. The most recent n // image revisions for a tag will be preserved, where n is specified by the // algorithm's tagRevisionsToKeep. Image revisions older than n are candidates // for pruning. if the image stream's age is at least as old as the minimum // threshold in algorithm. Otherwise, if the image stream is younger than the // threshold, all image revisions for that stream are ineligible for pruning. // // addImageStreamsToGraph also adds references from each stream to all the // layers it references (via each image a stream references). func addImageStreamsToGraph(g graph.Graph, streams *imageapi.ImageStreamList, algorithm pruneAlgorithm) { for i := range streams.Items { stream := &streams.Items[i] glog.V(4).Infof("Examining ImageStream %s/%s", stream.Namespace, stream.Name) // use a weak reference for old image revisions by default oldImageRevisionReferenceKind := graph.WeakReferencedImageGraphEdgeKind age := util.Now().Sub(stream.CreationTimestamp.Time) if age < algorithm.keepYoungerThan { // stream's age is below threshold - use a strong reference for old image revisions instead glog.V(4).Infof("Stream %s/%s is below age threshold - none of its images are eligible for pruning", stream.Namespace, stream.Name) oldImageRevisionReferenceKind = graph.ReferencedImageGraphEdgeKind } glog.V(4).Infof("Adding ImageStream %s/%s to graph", stream.Namespace, stream.Name) isNode := graph.ImageStream(g, stream) imageStreamNode := isNode.(*graph.ImageStreamNode) for tag, history := range stream.Status.Tags { for i := range history.Items { n := graph.FindImage(g, history.Items[i].Image) if n == nil { glog.V(1).Infof("Unable to find image %q in graph (from tag=%q, revision=%d, dockerImageReference=%s)", history.Items[i].Image, tag, i, history.Items[i].DockerImageReference) continue } imageNode := n.(*graph.ImageNode) var kind int switch { case i < algorithm.tagRevisionsToKeep: kind = graph.ReferencedImageGraphEdgeKind default: kind = oldImageRevisionReferenceKind } glog.V(4).Infof("Checking for existing strong reference from stream %s/%s to image %s", stream.Namespace, stream.Name, imageNode.Image.Name) if edge := g.EdgeBetween(imageStreamNode, imageNode); edge != nil && g.EdgeKind(edge) == graph.ReferencedImageGraphEdgeKind { glog.V(4).Infof("Strong reference found") continue } glog.V(4).Infof("Adding edge (kind=%d) from %q to %q", kind, imageStreamNode.UniqueName.UniqueName(), imageNode.UniqueName.UniqueName()) g.AddEdge(imageStreamNode, imageNode, kind) glog.V(4).Infof("Adding stream->layer references") // add stream -> layer references so we can prune them later for _, s := range g.Successors(imageNode) { if g.Kind(s) != graph.ImageLayerGraphKind { continue } glog.V(4).Infof("Adding reference from stream %q to layer %q", stream.Name, s.(*graph.ImageLayerNode).Layer) g.AddEdge(imageStreamNode, s, graph.ReferencedImageLayerGraphEdgeKind) } } } } }
func findBuildInputs(g osgraph.Graph, n graph.Node, covered osgraph.NodeSet) (base ImageTagLocation, source SourceLocation, err error) { // find inputs to the build for _, input := range g.Neighbors(n) { switch g.EdgeKind(g.EdgeBetween(n, input)) { case buildedges.BuildInputEdgeKind: if source != nil { // report this as an error (unexpected duplicate source) } covered.Add(input.ID()) source = input.(SourceLocation) case buildedges.BuildInputImageEdgeKind: if base != nil { // report this as an error (unexpected duplicate input build) } covered.Add(input.ID()) base = input.(ImageTagLocation) } } return }
// DescendentNodesByNodeKind starts at the root navigates down the root. Every edge is checked against the edgeChecker // to determine whether or not to follow it. The nodes at the tail end of every chased edge are then checked against the // the targetNodeKind. Matches are added to the return and every checked node then has its edges checked: lather, rinse, repeat func DescendentNodesByNodeKind(g osgraph.Graph, visitedNodes graphview.IntSet, node graph.Node, targetNodeKind string, edgeChecker osgraph.EdgeFunc) []graph.Node { if visitedNodes.Has(node.ID()) { return []graph.Node{} } visitedNodes.Insert(node.ID()) ret := []graph.Node{} for _, successor := range g.Successors(node) { edge := g.EdgeBetween(node, successor) if edgeChecker(osgraph.New(), node, successor, g.EdgeKinds(edge)) { if g.Kind(successor) == targetNodeKind { ret = append(ret, successor) } ret = append(ret, DescendentNodesByNodeKind(g, visitedNodes, successor, targetNodeKind, edgeChecker)...) } } return ret }
// ImagePipelineFromNode attempts to locate a build flow from the provided node. If no such // build flow can be located, false is returned. func ImagePipelineFromNode(g osgraph.Graph, n graph.Node, covered osgraph.NodeSet) (ImagePipeline, bool) { flow := ImagePipeline{} switch node := n.(type) { case *buildgraph.BuildConfigNode: covered.Add(n.ID()) base, src, _ := findBuildInputs(g, n, covered) flow.Build = node flow.BaseImage = base flow.Source = src return flow, true case ImageTagLocation: covered.Add(n.ID()) flow.Image = node for _, input := range g.Neighbors(n) { switch g.EdgeKind(g.EdgeBetween(n, input)) { case buildedges.BuildOutputEdgeKind: covered.Add(input.ID()) build := input.(*buildgraph.BuildConfigNode) if flow.Build != nil { // report this as an error (unexpected duplicate input build) } if build.BuildConfig == nil { // report this as as a missing build / broken link break } base, src, _ := findBuildInputs(g, input, covered) flow.Build = build flow.BaseImage = base flow.Source = src } } return flow, true default: return flow, false } }
// DeploymentPipelines returns a map of DeploymentConfigs to the deployment flows that create them, // extracted from the provided Graph. func DeploymentPipelines(g osgraph.Graph) (DeploymentPipelineMap, osgraph.NodeSet) { covered := make(osgraph.NodeSet) g = g.EdgeSubgraph(osgraph.ReverseGraphEdge) flows := make(DeploymentPipelineMap) for _, node := range g.NodeList() { switch t := node.(type) { case *deploygraph.DeploymentConfigNode: covered.Add(t.ID()) images := []ImagePipeline{} for _, n := range g.Neighbors(node) { // find incoming image edges only switch g.EdgeKind(g.EdgeBetween(node, n)) { case deployedges.TriggersDeploymentEdgeKind, deployedges.UsedInDeploymentEdgeKind: if flow, ok := ImagePipelineFromNode(g, n, covered); ok { images = append(images, flow) } } } output := []ImagePipeline{} // ensure the list of images is ordered the same as what is in the template if template := t.DeploymentConfig.Template.ControllerTemplate.Template; template != nil { deployedges.EachTemplateImage( &template.Spec, deployedges.DeploymentConfigHasTrigger(t.DeploymentConfig), func(image deployedges.TemplateImage, err error) { if err != nil { return } for i := range images { switch t := images[i].Image.(type) { case *imagegraph.ImageStreamTagNode: if image.Ref != nil { continue } from := image.From if t.ImageStream.Name != from.Name || t.ImageStream.Namespace != from.Namespace { continue } output = append(output, images[i]) return case *imagegraph.DockerImageRepositoryNode: if image.From != nil { continue } ref1, ref2 := t.Ref.Minimal(), image.Ref.DockerClientDefaults().Minimal() if ref1 != ref2 { continue } output = append(output, images[i]) return } } }, ) } flows[t] = output } } return flows, covered }
// 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 }
// edgeKind returns true if the edge from "from" to "to" is of the desired kind. func edgeKind(g graph.Graph, from, to gonum.Node, desiredKind int) bool { edge := g.EdgeBetween(from, to) kind := g.EdgeKind(edge) return kind == desiredKind }