Browse code

add pod and rc spec nodes

deads2k authored on 2015/06/23 21:32:16
Showing 13 changed files
... ...
@@ -3,9 +3,12 @@ package graph
3 3
 import (
4 4
 	"fmt"
5 5
 	"io"
6
+	"sort"
6 7
 
7 8
 	"github.com/gonum/graph"
8 9
 	"github.com/gonum/graph/concrete"
10
+
11
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
9 12
 )
10 13
 
11 14
 type Node struct {
... ...
@@ -109,6 +112,35 @@ func New() Graph {
109 109
 	}
110 110
 }
111 111
 
112
+func (g Graph) String() string {
113
+	ret := ""
114
+
115
+	nodeList := g.NodeList()
116
+	sort.Sort(SortedNodeList(nodeList))
117
+	for _, node := range nodeList {
118
+		ret += fmt.Sprintf("%d: %v\n", node.ID(), g.GraphDescriber.Name(node))
119
+
120
+		// can't use SuccessorEdges, because I want stable ordering
121
+		successors := g.Successors(node)
122
+		sort.Sort(SortedNodeList(successors))
123
+		for _, successor := range successors {
124
+			edge := g.EdgeBetween(node, successor)
125
+			kind := g.EdgeKind(edge)
126
+			ret += fmt.Sprintf("\t%v to %d: %v\n", kind, successor.ID(), g.GraphDescriber.Name(successor))
127
+		}
128
+	}
129
+
130
+	return ret
131
+}
132
+
133
+type SortedNodeList []graph.Node
134
+
135
+func (m SortedNodeList) Len() int      { return len(m) }
136
+func (m SortedNodeList) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
137
+func (m SortedNodeList) Less(i, j int) bool {
138
+	return m[i].ID() < m[j].ID()
139
+}
140
+
112 141
 // RootNodes returns all the roots of this graph.
113 142
 func (g Graph) RootNodes() []graph.Node {
114 143
 	roots := []graph.Node{}
... ...
@@ -153,6 +185,38 @@ func (g Graph) SuccessorEdges(node graph.Node, fn EdgeFunc, edgeKind ...string)
153 153
 	}
154 154
 }
155 155
 
156
+// OutboundEdges returns all the outbound edges from node that are in the list of edgeKinds
157
+// if edgeKinds is empty, then all edges are returned
158
+func (g Graph) OutboundEdges(node graph.Node, edgeKinds ...string) []graph.Edge {
159
+	ret := []graph.Edge{}
160
+
161
+	allowedKinds := util.NewStringSet(edgeKinds...)
162
+	for _, n := range g.Successors(node) {
163
+		edge := g.EdgeBetween(n, node)
164
+		if len(allowedKinds) == 0 || allowedKinds.Has(g.EdgeKind(edge)) {
165
+			ret = append(ret, edge)
166
+		}
167
+	}
168
+
169
+	return ret
170
+}
171
+
172
+// InboundEdges returns all the inbound edges to node that are in the list of edgeKinds
173
+// if edgeKinds is empty, then all edges are returned
174
+func (g Graph) InboundEdges(node graph.Node, edgeKinds ...string) []graph.Edge {
175
+	ret := []graph.Edge{}
176
+
177
+	allowedKinds := util.NewStringSet(edgeKinds...)
178
+	for _, n := range g.Predecessors(node) {
179
+		edge := g.EdgeBetween(n, node)
180
+		if len(allowedKinds) == 0 || allowedKinds.Has(g.EdgeKind(edge)) {
181
+			ret = append(ret, edge)
182
+		}
183
+	}
184
+
185
+	return ret
186
+}
187
+
156 188
 func (g Graph) EdgeList() []graph.Edge {
157 189
 	return g.internal.EdgeList()
158 190
 }
... ...
@@ -163,6 +227,16 @@ func (g Graph) AddNode(n graph.Node) {
163 163
 
164 164
 // AddEdge implements MutableUniqueGraph
165 165
 func (g Graph) AddEdge(head, tail graph.Node, edgeKind string) {
166
+	// a Contains edge has semantic meaning for osgraph.Graph objects.  It never makes sense
167
+	// to allow a single object to be "contained" by multiple nodes.
168
+	if edgeKind == ContainsEdgeKind {
169
+		// check incoming edges on the tail to be certain that we aren't already contained
170
+		containsEdges := g.InboundEdges(tail, ContainsEdgeKind)
171
+		if len(containsEdges) != 0 {
172
+			// TODO consider changing the AddEdge API to make this cleaner.  This is a pretty severe programming error
173
+			panic(fmt.Sprintf("%v is already contained by %v", tail, containsEdges))
174
+		}
175
+	}
166 176
 	g.internal.AddDirectedEdge(NewEdge(head, tail, edgeKind), 1)
167 177
 }
168 178
 
... ...
@@ -3,6 +3,8 @@ package graph
3 3
 import (
4 4
 	"fmt"
5 5
 
6
+	"github.com/gonum/graph"
7
+
6 8
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
7 9
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
8 10
 )
... ...
@@ -12,8 +14,13 @@ var (
12 12
 )
13 13
 
14 14
 var (
15
-	UnknownEdgeKind      = "UnknownEdge"
15
+	UnknownEdgeKind = "UnknownEdge"
16
+	// ReferencedByEdgeKind is the kind to use if you're building reverse links that don't have a specific edge in the other direction
17
+	// other uses are discouraged.  You should create a kind for your edge
16 18
 	ReferencedByEdgeKind = "ReferencedBy"
19
+	// ContainsEdgeKind is the kind to use if one node's contents logically contain another node's contents.  A given node can only have
20
+	// a single inbound Contais edge.  The code does not prevent contains cycles, but that's insane, don't do that.
21
+	ContainsEdgeKind = "Contains"
17 22
 )
18 23
 
19 24
 func GetUniqueRuntimeObjectNodeName(nodeKind string, obj runtime.Object) UniqueName {
... ...
@@ -24,3 +31,43 @@ func GetUniqueRuntimeObjectNodeName(nodeKind string, obj runtime.Object) UniqueN
24 24
 
25 25
 	return UniqueName(fmt.Sprintf("%s|%s/%s", nodeKind, meta.Namespace, meta.Name))
26 26
 }
27
+
28
+// GetTopLevelContainerNode traverses the reverse ContainsEdgeKind edges until it finds a node
29
+// that does not have an inbound ContainsEdgeKind edge.  This could be the node itself
30
+func GetTopLevelContainerNode(g Graph, containedNode graph.Node) graph.Node {
31
+	// my kingdom for a LinkedHashSet
32
+	visited := map[int]bool{}
33
+	prevContainingNode := containedNode
34
+
35
+	for {
36
+		visited[prevContainingNode.ID()] = true
37
+		currContainingNode := GetContainingNode(g, prevContainingNode)
38
+
39
+		if currContainingNode == nil {
40
+			return prevContainingNode
41
+		}
42
+		if _, alreadyVisited := visited[currContainingNode.ID()]; alreadyVisited {
43
+			panic(fmt.Sprintf("contains cycle in %v", visited))
44
+		}
45
+
46
+		prevContainingNode = currContainingNode
47
+	}
48
+
49
+	// can't happen
50
+	panic(fmt.Sprintf("math failed %v", visited))
51
+	return nil
52
+}
53
+
54
+// GetContainingNode returns the direct predecessor that is linked to the node by a ContainsEdgeKind.  It returns
55
+// nil if no container is found.
56
+func GetContainingNode(g Graph, containedNode graph.Node) graph.Node {
57
+	for _, node := range g.Predecessors(containedNode) {
58
+		edge := g.EdgeBetween(node, containedNode)
59
+
60
+		if g.EdgeKind(edge) == ContainsEdgeKind {
61
+			return node
62
+		}
63
+	}
64
+
65
+	return nil
66
+}
27 67
new file mode 100644
... ...
@@ -0,0 +1,69 @@
0
+package graph
1
+
2
+import (
3
+	"testing"
4
+
5
+	"github.com/gonum/graph"
6
+)
7
+
8
+func makeTestNode(g MutableUniqueGraph, name string) graph.Node {
9
+	return EnsureUnique(g,
10
+		UniqueName(name),
11
+		func(node Node) graph.Node {
12
+			return node
13
+		},
14
+	)
15
+}
16
+
17
+func TestContainsNavigation(t *testing.T) {
18
+	g := New()
19
+
20
+	aNode := makeTestNode(g, "a")
21
+	bNode := makeTestNode(g, "b")
22
+	cNode := makeTestNode(g, "c")
23
+
24
+	g.AddEdge(aNode, bNode, ContainsEdgeKind)
25
+	g.AddEdge(bNode, cNode, ContainsEdgeKind)
26
+
27
+	topA := GetTopLevelContainerNode(g, aNode)
28
+	if e, a := aNode.ID(), topA.ID(); e != a {
29
+		t.Errorf("expected %v, got %v", e, a)
30
+	}
31
+
32
+	topB := GetTopLevelContainerNode(g, bNode)
33
+	if e, a := aNode.ID(), topB.ID(); e != a {
34
+		t.Errorf("expected %v, got %v", e, a)
35
+	}
36
+
37
+	topC := GetTopLevelContainerNode(g, cNode)
38
+	if e, a := aNode.ID(), topC.ID(); e != a {
39
+		t.Errorf("expected %v, got %v", e, a)
40
+	}
41
+
42
+	containsB := GetContainingNode(g, bNode)
43
+	if e, a := aNode.ID(), containsB.ID(); e != a {
44
+		t.Errorf("expected %v, got %v", e, a)
45
+	}
46
+
47
+	containsC := GetContainingNode(g, cNode)
48
+	if e, a := bNode.ID(), containsC.ID(); e != a {
49
+		t.Errorf("expected %v, got %v", e, a)
50
+	}
51
+}
52
+
53
+func TestOnlyOneContainseEdge(t *testing.T) {
54
+	g := New()
55
+
56
+	aNode := makeTestNode(g, "a")
57
+	bNode := makeTestNode(g, "b")
58
+	cNode := makeTestNode(g, "c")
59
+
60
+	g.AddEdge(aNode, bNode, ContainsEdgeKind)
61
+
62
+	defer func() {
63
+		if r := recover(); r == nil {
64
+			t.Errorf("expected to recover panic!")
65
+		}
66
+	}()
67
+	g.AddEdge(cNode, bNode, ContainsEdgeKind)
68
+}
... ...
@@ -9,6 +9,7 @@ import (
9 9
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
10 10
 
11 11
 	osgraph "github.com/openshift/origin/pkg/api/graph"
12
+	kubeedges "github.com/openshift/origin/pkg/api/kubegraph"
12 13
 	kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
13 14
 	buildedges "github.com/openshift/origin/pkg/build/graph"
14 15
 	buildgraph "github.com/openshift/origin/pkg/build/graph/nodes"
... ...
@@ -37,6 +38,22 @@ type DeploymentFlow struct {
37 37
 	Images     []ImagePipeline
38 38
 }
39 39
 
40
+// ServiceReference is a service and the DeploymentConfigs it covers
41
+type ServiceReference struct {
42
+	Service     *kubegraph.ServiceNode
43
+	CoveredDCs  []*deploygraph.DeploymentConfigNode
44
+	CoveredRCs  []*kubegraph.ReplicationControllerNode
45
+	CoveredPods []*kubegraph.PodNode
46
+}
47
+
48
+// ServiceGroup is a related set of resources that should be displayed together
49
+// logically. They are usually sorted internally.
50
+type ServiceGroup struct {
51
+	Services    []ServiceReference
52
+	Deployments []DeploymentFlow
53
+	Builds      []ImagePipeline
54
+}
55
+
40 56
 // ImageTagLocation identifies the source or destination of an image. Represents
41 57
 // both a tag in a Docker image repository, as well as a tag in an OpenShift image stream.
42 58
 type ImageTagLocation interface {
... ...
@@ -195,16 +212,28 @@ func ServiceAndDeploymentGroups(g osgraph.Graph) []ServiceGroup {
195 195
 		svcs, dcs, _ := matches[0], matches[1], matches[2]
196 196
 
197 197
 		for _, n := range svcs {
198
-			covers := []*deploygraph.DeploymentConfigNode{}
198
+			coveredDCs := []*deploygraph.DeploymentConfigNode{}
199
+			coveredRCs := []*kubegraph.ReplicationControllerNode{}
200
+			coveredPods := []*kubegraph.PodNode{}
199 201
 			for _, neighbor := range other.Neighbors(n) {
200 202
 				switch other.EdgeKind(g.EdgeBetween(neighbor, n)) {
201
-				case deployedges.ExposedThroughServiceEdgeKind:
202
-					covers = append(covers, neighbor.(*deploygraph.DeploymentConfigNode))
203
+				case kubeedges.ExposedThroughServiceEdgeKind:
204
+					containerNode := osgraph.GetTopLevelContainerNode(g, neighbor)
205
+					switch castCoveredNode := containerNode.(type) {
206
+					case *deploygraph.DeploymentConfigNode:
207
+						coveredDCs = append(coveredDCs, castCoveredNode)
208
+					case *kubegraph.ReplicationControllerNode:
209
+						coveredRCs = append(coveredRCs, castCoveredNode)
210
+					case *kubegraph.PodNode:
211
+						coveredPods = append(coveredPods, castCoveredNode)
212
+					}
203 213
 				}
204 214
 			}
205 215
 			group.Services = append(group.Services, ServiceReference{
206
-				Service: n.(*kubegraph.ServiceNode),
207
-				Covers:  covers,
216
+				Service:     n.(*kubegraph.ServiceNode),
217
+				CoveredDCs:  coveredDCs,
218
+				CoveredRCs:  coveredRCs,
219
+				CoveredPods: coveredPods,
208 220
 			})
209 221
 		}
210 222
 		sort.Sort(SortedServiceReferences(group.Services))
... ...
@@ -243,7 +272,7 @@ func ServiceAndDeploymentGroups(g osgraph.Graph) []ServiceGroup {
243 243
 // Services and DeploymentConfigs.
244 244
 func UncoveredDeploymentFlowEdges(covered osgraph.NodeSet) osgraph.EdgeFunc {
245 245
 	return func(g osgraph.Interface, head, tail graph.Node, edgeKind string) bool {
246
-		if edgeKind == deployedges.ExposedThroughServiceEdgeKind {
246
+		if edgeKind == kubeedges.ExposedThroughServiceEdgeKind {
247 247
 			return osgraph.AddReversedEdge(g, head, tail, osgraph.ReferencedByEdgeKind)
248 248
 		}
249 249
 		if covered.Has(head.ID()) && covered.Has(tail.ID()) {
... ...
@@ -265,20 +294,6 @@ func UncoveredDeploymentFlowNodes(covered osgraph.NodeSet) osgraph.NodeFunc {
265 265
 	}
266 266
 }
267 267
 
268
-// ServiceReference is a service and the DeploymentConfigs it covers
269
-type ServiceReference struct {
270
-	Service *kubegraph.ServiceNode
271
-	Covers  []*deploygraph.DeploymentConfigNode
272
-}
273
-
274
-// ServiceGroup is a related set of resources that should be displayed together
275
-// logically. They are usually sorted internally.
276
-type ServiceGroup struct {
277
-	Services    []ServiceReference
278
-	Deployments []DeploymentFlow
279
-	Builds      []ImagePipeline
280
-}
281
-
282 268
 // Sorts on the provided objects.
283 269
 
284 270
 type SortedServiceReferences []ServiceReference
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
12 12
 
13 13
 	osgraph "github.com/openshift/origin/pkg/api/graph"
14
+	kubeedges "github.com/openshift/origin/pkg/api/kubegraph"
14 15
 	kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
15 16
 	buildapi "github.com/openshift/origin/pkg/build/api"
16 17
 	buildedges "github.com/openshift/origin/pkg/build/graph"
... ...
@@ -52,7 +53,7 @@ func TestGraph(t *testing.T) {
52 52
 		},
53 53
 	}
54 54
 
55
-	bcNode := buildgraph.EnsureBuildConfigNode(g, &buildapi.BuildConfig{
55
+	bc1Node := buildgraph.EnsureBuildConfigNode(g, &buildapi.BuildConfig{
56 56
 		ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "build1"},
57 57
 		Triggers: []buildapi.BuildTriggerPolicy{
58 58
 			{
... ...
@@ -72,7 +73,7 @@ func TestGraph(t *testing.T) {
72 72
 			},
73 73
 		},
74 74
 	})
75
-	buildedges.JoinBuilds(bcNode, builds)
75
+	buildedges.JoinBuilds(bc1Node, builds)
76 76
 	bcTestNode := buildgraph.EnsureBuildConfigNode(g, &buildapi.BuildConfig{
77 77
 		ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "test"},
78 78
 		Parameters: buildapi.BuildParameters{
... ...
@@ -178,13 +179,14 @@ func TestGraph(t *testing.T) {
178 178
 		},
179 179
 	})
180 180
 
181
-	deployedges.AddAllFullfillingDeploymentConfigEdges(g)
181
+	kubeedges.AddAllExposedPodTemplateSpecEdges(g)
182 182
 	buildedges.AddAllInputOutputEdges(g)
183 183
 	deployedges.AddAllTriggerEdges(g)
184 184
 
185
+	t.Log(g)
186
+
185 187
 	ir, dc, bc, other := 0, 0, 0, 0
186 188
 	for _, node := range g.NodeList() {
187
-		t.Logf("node: %d %v", node.ID(), node)
188 189
 		switch g.Object(node).(type) {
189 190
 		case *deployapi.DeploymentConfig:
190 191
 			if g.Kind(node) != deploygraph.DeploymentConfigNodeKind {
... ...
@@ -206,18 +208,14 @@ func TestGraph(t *testing.T) {
206 206
 			other++
207 207
 		}
208 208
 	}
209
-	if dc != 2 || bc != 3 || ir != 3 || other != 6 {
209
+
210
+	if dc != 2 || bc != 3 || ir != 3 || other != 12 {
210 211
 		t.Errorf("unexpected nodes: %d %d %d %d", dc, bc, ir, other)
211 212
 	}
212 213
 	for _, edge := range g.EdgeList() {
213 214
 		if g.EdgeKind(edge) == osgraph.UnknownEdgeKind {
214 215
 			t.Errorf("edge reported unknown kind: %#v", edge)
215 216
 		}
216
-		t.Logf("edge: %v", edge)
217
-	}
218
-	reverse := g.EdgeSubgraph(osgraph.ReverseGraphEdge)
219
-	for _, edge := range reverse.EdgeList() {
220
-		t.Logf("redge: %v", edge)
221 217
 	}
222 218
 
223 219
 	// imagestreamtag default/other:base-image
... ...
@@ -233,7 +231,6 @@ func TestGraph(t *testing.T) {
233 233
 	if edge == nil {
234 234
 		t.Fatalf("failed to find edge between %d and %d", bcTestNode.ID(), istID)
235 235
 	}
236
-
237 236
 	if len(g.SubgraphWithNodes([]graph.Node{edge.Head(), edge.Tail()}, osgraph.ExistingDirectEdge).EdgeList()) != 1 {
238 237
 		t.Fatalf("expected one edge")
239 238
 	}
240 239
new file mode 100644
... ...
@@ -0,0 +1,68 @@
0
+package kubegraph
1
+
2
+import (
3
+	"github.com/gonum/graph"
4
+
5
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
6
+
7
+	osgraph "github.com/openshift/origin/pkg/api/graph"
8
+	kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
9
+)
10
+
11
+const (
12
+	// ExposedThroughServiceEdgeKind is an edge that goes from a podtemplatespec or a pod to service.
13
+	// The head should make the service's selector
14
+	ExposedThroughServiceEdgeKind = "ExposedThroughService"
15
+)
16
+
17
+// AddExposedPodTemplateSpecEdges ensures that a directed edge exists between a service and all the PodTemplateSpecs
18
+// in the graph that match the service selector
19
+func AddExposedPodTemplateSpecEdges(g osgraph.MutableUniqueGraph, node *kubegraph.ServiceNode) {
20
+	if node.Service.Spec.Selector == nil {
21
+		return
22
+	}
23
+	query := labels.SelectorFromSet(node.Service.Spec.Selector)
24
+	for _, n := range g.(graph.Graph).NodeList() {
25
+		switch target := n.(type) {
26
+		case *kubegraph.PodTemplateSpecNode:
27
+			if query.Matches(labels.Set(target.PodTemplateSpec.Labels)) {
28
+				g.AddEdge(target, node, ExposedThroughServiceEdgeKind)
29
+			}
30
+		}
31
+	}
32
+}
33
+
34
+// AddAllExposedPodTemplateSpecEdges calls AddExposedPodTemplateSpecEdges for every ServiceNode in the graph
35
+func AddAllExposedPodTemplateSpecEdges(g osgraph.MutableUniqueGraph) {
36
+	for _, node := range g.(graph.Graph).NodeList() {
37
+		if serviceNode, ok := node.(*kubegraph.ServiceNode); ok {
38
+			AddExposedPodTemplateSpecEdges(g, serviceNode)
39
+		}
40
+	}
41
+}
42
+
43
+// AddExposedPodEdges ensures that a directed edge exists between a service and all the pods
44
+// in the graph that match the service selector
45
+func AddExposedPodEdges(g osgraph.MutableUniqueGraph, node *kubegraph.ServiceNode) {
46
+	if node.Service.Spec.Selector == nil {
47
+		return
48
+	}
49
+	query := labels.SelectorFromSet(node.Service.Spec.Selector)
50
+	for _, n := range g.(graph.Graph).NodeList() {
51
+		switch target := n.(type) {
52
+		case *kubegraph.PodNode:
53
+			if query.Matches(labels.Set(target.Labels)) {
54
+				g.AddEdge(target, node, ExposedThroughServiceEdgeKind)
55
+			}
56
+		}
57
+	}
58
+}
59
+
60
+// AddAllExposedPodEdges calls AddExposedPodEdges for every ServiceNode in the graph
61
+func AddAllExposedPodEdges(g osgraph.MutableUniqueGraph) {
62
+	for _, node := range g.(graph.Graph).NodeList() {
63
+		if serviceNode, ok := node.(*kubegraph.ServiceNode); ok {
64
+			AddExposedPodEdges(g, serviceNode)
65
+		}
66
+	}
67
+}
... ...
@@ -9,12 +9,27 @@ import (
9 9
 )
10 10
 
11 11
 func EnsurePodNode(g osgraph.MutableUniqueGraph, pod *kapi.Pod) *PodNode {
12
-	return osgraph.EnsureUnique(g,
13
-		PodNodeName(pod),
12
+	podNodeName := PodNodeName(pod)
13
+	podNode := osgraph.EnsureUnique(g,
14
+		podNodeName,
14 15
 		func(node osgraph.Node) graph.Node {
15 16
 			return &PodNode{node, pod}
16 17
 		},
17 18
 	).(*PodNode)
19
+
20
+	podSpecNode := EnsurePodSpecNode(g, &pod.Spec, podNodeName)
21
+	g.AddEdge(podNode, podSpecNode, osgraph.ContainsEdgeKind)
22
+
23
+	return podNode
24
+}
25
+
26
+func EnsurePodSpecNode(g osgraph.MutableUniqueGraph, podSpec *kapi.PodSpec, ownerName osgraph.UniqueName) *PodSpecNode {
27
+	return osgraph.EnsureUnique(g,
28
+		PodSpecNodeName(podSpec, ownerName),
29
+		func(node osgraph.Node) graph.Node {
30
+			return &PodSpecNode{node, podSpec, ownerName}
31
+		},
32
+	).(*PodSpecNode)
18 33
 }
19 34
 
20 35
 // EnsureServiceNode adds the provided service to the graph if it does not already exist.
... ...
@@ -29,10 +44,48 @@ func EnsureServiceNode(g osgraph.MutableUniqueGraph, svc *kapi.Service) *Service
29 29
 
30 30
 // EnsureReplicationControllerNode adds a graph node for the ReplicationController if it does not already exist.
31 31
 func EnsureReplicationControllerNode(g osgraph.MutableUniqueGraph, rc *kapi.ReplicationController) *ReplicationControllerNode {
32
-	return osgraph.EnsureUnique(g,
33
-		ReplicationControllerNodeName(rc),
32
+	rcNodeName := ReplicationControllerNodeName(rc)
33
+	rcNode := osgraph.EnsureUnique(g,
34
+		rcNodeName,
34 35
 		func(node osgraph.Node) graph.Node {
35 36
 			return &ReplicationControllerNode{node, rc}
36 37
 		},
37 38
 	).(*ReplicationControllerNode)
39
+
40
+	rcSpecNode := EnsureReplicationControllerSpecNode(g, &rc.Spec, rcNodeName)
41
+	g.AddEdge(rcNode, rcSpecNode, osgraph.ContainsEdgeKind)
42
+
43
+	return rcNode
44
+}
45
+
46
+func EnsureReplicationControllerSpecNode(g osgraph.MutableUniqueGraph, rcSpec *kapi.ReplicationControllerSpec, ownerName osgraph.UniqueName) *ReplicationControllerSpecNode {
47
+	rcSpecName := ReplicationControllerSpecNodeName(rcSpec, ownerName)
48
+	rcSpecNode := osgraph.EnsureUnique(g,
49
+		rcSpecName,
50
+		func(node osgraph.Node) graph.Node {
51
+			return &ReplicationControllerSpecNode{node, rcSpec, ownerName}
52
+		},
53
+	).(*ReplicationControllerSpecNode)
54
+
55
+	if rcSpec.Template != nil {
56
+		ptSpecNode := EnsurePodTemplateSpecNode(g, rcSpec.Template, rcSpecName)
57
+		g.AddEdge(rcSpecNode, ptSpecNode, osgraph.ContainsEdgeKind)
58
+	}
59
+
60
+	return rcSpecNode
61
+}
62
+
63
+func EnsurePodTemplateSpecNode(g osgraph.MutableUniqueGraph, ptSpec *kapi.PodTemplateSpec, ownerName osgraph.UniqueName) *PodTemplateSpecNode {
64
+	ptSpecName := PodTemplateSpecNodeName(ptSpec, ownerName)
65
+	ptSpecNode := osgraph.EnsureUnique(g,
66
+		ptSpecName,
67
+		func(node osgraph.Node) graph.Node {
68
+			return &PodTemplateSpecNode{node, ptSpec, ownerName}
69
+		},
70
+	).(*PodTemplateSpecNode)
71
+
72
+	podSpecNode := EnsurePodSpecNode(g, &ptSpec.Spec, ptSpecName)
73
+	g.AddEdge(ptSpecNode, podSpecNode, osgraph.ContainsEdgeKind)
74
+
75
+	return ptSpecNode
38 76
 }
39 77
new file mode 100644
... ...
@@ -0,0 +1,90 @@
0
+package nodes
1
+
2
+import (
3
+	"testing"
4
+
5
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
6
+
7
+	osgraph "github.com/openshift/origin/pkg/api/graph"
8
+)
9
+
10
+func TestPodSpecNode(t *testing.T) {
11
+	g := osgraph.New()
12
+
13
+	pod := &kapi.Pod{}
14
+	pod.Namespace = "ns"
15
+	pod.Name = "foo"
16
+	pod.Spec.Host = "any-host"
17
+
18
+	podNode := EnsurePodNode(g, pod)
19
+
20
+	if len(g.NodeList()) != 2 {
21
+		t.Errorf("expected 2 nodes, got %v", g.NodeList())
22
+	}
23
+
24
+	if len(g.EdgeList()) != 1 {
25
+		t.Errorf("expected 1 edge, got %v", g.EdgeList())
26
+	}
27
+
28
+	edge := g.EdgeList()[0]
29
+	if g.EdgeKind(edge) != osgraph.ContainsEdgeKind {
30
+		t.Errorf("expected %v, got %v", osgraph.ContainsEdgeKind, g.EdgeKind(edge))
31
+	}
32
+	if edge.Head().ID() != podNode.ID() {
33
+		t.Errorf("expected %v, got %v", podNode.ID(), edge.Head())
34
+	}
35
+}
36
+
37
+func TestReplicationControllerSpecNode(t *testing.T) {
38
+	g := osgraph.New()
39
+
40
+	rc := &kapi.ReplicationController{}
41
+	rc.Namespace = "ns"
42
+	rc.Name = "foo"
43
+	rc.Spec.Template = &kapi.PodTemplateSpec{}
44
+
45
+	rcNode := EnsureReplicationControllerNode(g, rc)
46
+
47
+	if len(g.NodeList()) != 4 {
48
+		t.Errorf("expected 4 nodes, got %v", g.NodeList())
49
+	}
50
+
51
+	if len(g.EdgeList()) != 3 {
52
+		t.Errorf("expected 3 edge, got %v", g.EdgeList())
53
+	}
54
+
55
+	rcEdges := g.OutboundEdges(rcNode)
56
+	if len(rcEdges) != 1 {
57
+		t.Fatalf("expected 1 edge, got %v", rcEdges)
58
+	}
59
+	if g.EdgeKind(rcEdges[0]) != osgraph.ContainsEdgeKind {
60
+		t.Errorf("expected %v, got %v", osgraph.ContainsEdgeKind, rcEdges[0])
61
+	}
62
+
63
+	uncastRCSpec := rcEdges[0].Tail()
64
+	rcSpec, ok := uncastRCSpec.(*ReplicationControllerSpecNode)
65
+	if !ok {
66
+		t.Fatalf("expected rcSpec, got %v", uncastRCSpec)
67
+	}
68
+	rcSpecEdges := g.OutboundEdges(rcSpec)
69
+	if len(rcSpecEdges) != 1 {
70
+		t.Fatalf("expected 1 edge, got %v", rcSpecEdges)
71
+	}
72
+	if g.EdgeKind(rcSpecEdges[0]) != osgraph.ContainsEdgeKind {
73
+		t.Errorf("expected %v, got %v", osgraph.ContainsEdgeKind, rcSpecEdges[0])
74
+	}
75
+
76
+	uncastPTSpec := rcSpecEdges[0].Tail()
77
+	ptSpec, ok := uncastPTSpec.(*PodTemplateSpecNode)
78
+	if !ok {
79
+		t.Fatalf("expected ptspec, got %v", uncastPTSpec)
80
+	}
81
+	ptSpecEdges := g.OutboundEdges(ptSpec)
82
+	if len(ptSpecEdges) != 1 {
83
+		t.Fatalf("expected 1 edge, got %v", ptSpecEdges)
84
+	}
85
+	if g.EdgeKind(ptSpecEdges[0]) != osgraph.ContainsEdgeKind {
86
+		t.Errorf("expected %v, got %v", osgraph.ContainsEdgeKind, ptSpecEdges[0])
87
+	}
88
+
89
+}
... ...
@@ -10,9 +10,12 @@ import (
10 10
 )
11 11
 
12 12
 var (
13
-	ServiceNodeKind               = reflect.TypeOf(kapi.Service{}).Name()
14
-	PodNodeKind                   = reflect.TypeOf(kapi.Pod{}).Name()
15
-	ReplicationControllerNodeKind = reflect.TypeOf(kapi.ReplicationController{}).Name()
13
+	ServiceNodeKind                   = reflect.TypeOf(kapi.Service{}).Name()
14
+	PodNodeKind                       = reflect.TypeOf(kapi.Pod{}).Name()
15
+	PodSpecNodeKind                   = reflect.TypeOf(kapi.PodSpec{}).Name()
16
+	PodTemplateSpecNodeKind           = reflect.TypeOf(kapi.PodTemplateSpec{}).Name()
17
+	ReplicationControllerNodeKind     = reflect.TypeOf(kapi.ReplicationController{}).Name()
18
+	ReplicationControllerSpecNodeKind = reflect.TypeOf(kapi.ReplicationControllerSpec{}).Name()
16 19
 )
17 20
 
18 21
 func ServiceNodeName(o *kapi.Service) osgraph.UniqueName {
... ...
@@ -53,10 +56,41 @@ func (n PodNode) String() string {
53 53
 	return fmt.Sprintf("<pod %s/%s>", n.Namespace, n.Name)
54 54
 }
55 55
 
56
+func (n PodNode) UniqueName() osgraph.UniqueName {
57
+	return PodNodeName(n.Pod)
58
+}
59
+
56 60
 func (*PodNode) Kind() string {
57 61
 	return PodNodeKind
58 62
 }
59 63
 
64
+func PodSpecNodeName(o *kapi.PodSpec, ownerName osgraph.UniqueName) osgraph.UniqueName {
65
+	return osgraph.UniqueName(fmt.Sprintf("%s|%v", PodSpecNodeKind, ownerName))
66
+}
67
+
68
+type PodSpecNode struct {
69
+	osgraph.Node
70
+	*kapi.PodSpec
71
+
72
+	OwnerName osgraph.UniqueName
73
+}
74
+
75
+func (n PodSpecNode) Object() interface{} {
76
+	return n.PodSpec
77
+}
78
+
79
+func (n PodSpecNode) String() string {
80
+	return string(n.UniqueName())
81
+}
82
+
83
+func (n PodSpecNode) UniqueName() osgraph.UniqueName {
84
+	return PodSpecNodeName(n.PodSpec, n.OwnerName)
85
+}
86
+
87
+func (*PodSpecNode) Kind() string {
88
+	return PodSpecNodeKind
89
+}
90
+
60 91
 func ReplicationControllerNodeName(o *kapi.ReplicationController) osgraph.UniqueName {
61 92
 	return osgraph.GetUniqueRuntimeObjectNodeName(ReplicationControllerNodeKind, o)
62 93
 }
... ...
@@ -74,6 +108,64 @@ func (n ReplicationControllerNode) String() string {
74 74
 	return fmt.Sprintf("<replicationcontroller %s/%s>", n.Namespace, n.Name)
75 75
 }
76 76
 
77
+func (n ReplicationControllerNode) UniqueName() osgraph.UniqueName {
78
+	return ReplicationControllerNodeName(n.ReplicationController)
79
+}
80
+
77 81
 func (*ReplicationControllerNode) Kind() string {
78 82
 	return ReplicationControllerNodeKind
79 83
 }
84
+
85
+func ReplicationControllerSpecNodeName(o *kapi.ReplicationControllerSpec, ownerName osgraph.UniqueName) osgraph.UniqueName {
86
+	return osgraph.UniqueName(fmt.Sprintf("%s|%v", ReplicationControllerSpecNodeKind, ownerName))
87
+}
88
+
89
+type ReplicationControllerSpecNode struct {
90
+	osgraph.Node
91
+	*kapi.ReplicationControllerSpec
92
+
93
+	OwnerName osgraph.UniqueName
94
+}
95
+
96
+func (n ReplicationControllerSpecNode) Object() interface{} {
97
+	return n.ReplicationControllerSpec
98
+}
99
+
100
+func (n ReplicationControllerSpecNode) String() string {
101
+	return string(n.UniqueName())
102
+}
103
+
104
+func (n ReplicationControllerSpecNode) UniqueName() osgraph.UniqueName {
105
+	return ReplicationControllerSpecNodeName(n.ReplicationControllerSpec, n.OwnerName)
106
+}
107
+
108
+func (*ReplicationControllerSpecNode) Kind() string {
109
+	return ReplicationControllerSpecNodeKind
110
+}
111
+
112
+func PodTemplateSpecNodeName(o *kapi.PodTemplateSpec, ownerName osgraph.UniqueName) osgraph.UniqueName {
113
+	return osgraph.UniqueName(fmt.Sprintf("%s|%v", PodTemplateSpecNodeKind, ownerName))
114
+}
115
+
116
+type PodTemplateSpecNode struct {
117
+	osgraph.Node
118
+	*kapi.PodTemplateSpec
119
+
120
+	OwnerName osgraph.UniqueName
121
+}
122
+
123
+func (n PodTemplateSpecNode) Object() interface{} {
124
+	return n.PodTemplateSpec
125
+}
126
+
127
+func (n PodTemplateSpecNode) String() string {
128
+	return string(n.UniqueName())
129
+}
130
+
131
+func (n PodTemplateSpecNode) UniqueName() osgraph.UniqueName {
132
+	return PodTemplateSpecNodeName(n.PodTemplateSpec, n.OwnerName)
133
+}
134
+
135
+func (*PodTemplateSpecNode) Kind() string {
136
+	return PodTemplateSpecNodeKind
137
+}
... ...
@@ -14,6 +14,7 @@ import (
14 14
 
15 15
 	"github.com/openshift/origin/pkg/api/graph"
16 16
 	graphveneers "github.com/openshift/origin/pkg/api/graph/veneers"
17
+	kubeedges "github.com/openshift/origin/pkg/api/kubegraph"
17 18
 	kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
18 19
 	buildapi "github.com/openshift/origin/pkg/build/api"
19 20
 	buildedges "github.com/openshift/origin/pkg/build/graph"
... ...
@@ -80,7 +81,7 @@ func (d *ProjectStatusDescriber) Describe(namespace, name string) (string, error
80 80
 	}
81 81
 	for i := range svcs.Items {
82 82
 		service := kubegraph.EnsureServiceNode(g, &svcs.Items[i])
83
-		deployedges.AddFullfillingDeploymentConfigEdges(g, service)
83
+		kubeedges.AddExposedPodTemplateSpecEdges(g, service)
84 84
 	}
85 85
 	groups := graphveneers.ServiceAndDeploymentGroups(g)
86 86
 
... ...
@@ -6,10 +6,8 @@ import (
6 6
 	"github.com/gonum/graph"
7 7
 
8 8
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
9
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
10 9
 
11 10
 	osgraph "github.com/openshift/origin/pkg/api/graph"
12
-	kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
13 11
 	deploygraph "github.com/openshift/origin/pkg/deploy/graph/nodes"
14 12
 	deployutil "github.com/openshift/origin/pkg/deploy/util"
15 13
 	imagegraph "github.com/openshift/origin/pkg/image/graph/nodes"
... ...
@@ -18,41 +16,8 @@ import (
18 18
 const (
19 19
 	TriggersDeploymentEdgeKind = "TriggersDeployment"
20 20
 	UsedInDeploymentEdgeKind   = "UsedInDeployment"
21
-
22
-	ExposedThroughServiceEdgeKind = "ExposedThroughService"
23 21
 )
24 22
 
25
-// AddFullfillingDeploymentConfigEdges ensures that a directed edge exists between all deployment configs and the
26
-// services that expose them (via label selectors).
27
-func AddFullfillingDeploymentConfigEdges(g osgraph.MutableUniqueGraph, node *kubegraph.ServiceNode) *kubegraph.ServiceNode {
28
-	if node.Service.Spec.Selector == nil {
29
-		return node
30
-	}
31
-	query := labels.SelectorFromSet(node.Service.Spec.Selector)
32
-	for _, n := range g.(graph.Graph).NodeList() {
33
-		switch target := n.(type) {
34
-		case *deploygraph.DeploymentConfigNode:
35
-			template := target.DeploymentConfig.Template.ControllerTemplate.Template
36
-			if template == nil {
37
-				continue
38
-			}
39
-			if query.Matches(labels.Set(template.Labels)) {
40
-				g.AddEdge(target, node, ExposedThroughServiceEdgeKind)
41
-			}
42
-		}
43
-	}
44
-
45
-	return node
46
-}
47
-
48
-func AddAllFullfillingDeploymentConfigEdges(g osgraph.MutableUniqueGraph) {
49
-	for _, node := range g.(graph.Graph).NodeList() {
50
-		if serviceNode, ok := node.(*kubegraph.ServiceNode); ok {
51
-			AddFullfillingDeploymentConfigEdges(g, serviceNode)
52
-		}
53
-	}
54
-}
55
-
56 23
 // AddTriggerEdges creates edges that point to named Docker image repositories for each image used in the deployment.
57 24
 func AddTriggerEdges(g osgraph.MutableUniqueGraph, node *deploygraph.DeploymentConfigNode) *deploygraph.DeploymentConfigNode {
58 25
 	rcTemplate := node.DeploymentConfig.Template.ControllerTemplate.Template
... ...
@@ -4,16 +4,23 @@ import (
4 4
 	"github.com/gonum/graph"
5 5
 
6 6
 	osgraph "github.com/openshift/origin/pkg/api/graph"
7
+	kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
7 8
 	depoyapi "github.com/openshift/origin/pkg/deploy/api"
8 9
 )
9 10
 
10 11
 // EnsureDeploymentConfigNode adds the provided deployment config to the graph if it does not exist
11
-func EnsureDeploymentConfigNode(g osgraph.MutableUniqueGraph, config *depoyapi.DeploymentConfig) *DeploymentConfigNode {
12
-	return osgraph.EnsureUnique(
12
+func EnsureDeploymentConfigNode(g osgraph.MutableUniqueGraph, dc *depoyapi.DeploymentConfig) *DeploymentConfigNode {
13
+	dcName := DeploymentConfigNodeName(dc)
14
+	dcNode := osgraph.EnsureUnique(
13 15
 		g,
14
-		DeploymentConfigNodeName(config),
16
+		dcName,
15 17
 		func(node osgraph.Node) graph.Node {
16
-			return &DeploymentConfigNode{Node: node, DeploymentConfig: config}
18
+			return &DeploymentConfigNode{Node: node, DeploymentConfig: dc}
17 19
 		},
18 20
 	).(*DeploymentConfigNode)
21
+
22
+	rcSpecNode := kubegraph.EnsureReplicationControllerSpecNode(g, &dc.Template.ControllerTemplate, dcName)
23
+	g.AddEdge(dcNode, rcSpecNode, osgraph.ContainsEdgeKind)
24
+
25
+	return dcNode
19 26
 }
20 27
new file mode 100644
... ...
@@ -0,0 +1,34 @@
0
+package nodes
1
+
2
+import (
3
+	"testing"
4
+
5
+	osgraph "github.com/openshift/origin/pkg/api/graph"
6
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
7
+)
8
+
9
+func TestDCRCSpecNode(t *testing.T) {
10
+	g := osgraph.New()
11
+
12
+	dc := &deployapi.DeploymentConfig{}
13
+	dc.Namespace = "ns"
14
+	dc.Name = "foo"
15
+
16
+	dcNode := EnsureDeploymentConfigNode(g, dc)
17
+
18
+	if len(g.NodeList()) != 2 {
19
+		t.Errorf("expected 2 nodes, got %v", g.NodeList())
20
+	}
21
+
22
+	if len(g.EdgeList()) != 1 {
23
+		t.Errorf("expected 2 edge, got %v", g.EdgeList())
24
+	}
25
+
26
+	edge := g.EdgeList()[0]
27
+	if g.EdgeKind(edge) != osgraph.ContainsEdgeKind {
28
+		t.Errorf("expected %v, got %v", osgraph.ContainsEdgeKind, g.EdgeKind(edge))
29
+	}
30
+	if edge.Head().ID() != dcNode.ID() {
31
+		t.Errorf("expected %v, got %v", dcNode.ID(), edge.Head())
32
+	}
33
+}