Browse code

add graph markers

deads2k authored on 2015/07/18 02:14:10
Showing 10 changed files
... ...
@@ -41,6 +41,7 @@ func NewReplicationController(g osgraph.Graph, rcNode *kubegraph.ReplicationCont
41 41
 
42 42
 	rcView := ReplicationController{}
43 43
 	rcView.RC = rcNode
44
+	rcView.ConflictingRCIDToPods = map[int][]*kubegraph.PodNode{}
44 45
 
45 46
 	for _, uncastPodNode := range g.PredecessorNodesByEdgeKind(rcNode, kubeedges.ManagedByRCEdgeKind) {
46 47
 		podNode := uncastPodNode.(*kubegraph.PodNode)
47 48
new file mode 100644
... ...
@@ -0,0 +1,96 @@
0
+package graph
1
+
2
+import (
3
+	"github.com/gonum/graph"
4
+)
5
+
6
+// Marker is a struct that describes something interesting on a Node
7
+type Marker struct {
8
+	// Node is the optional node that this message is attached to
9
+	Node graph.Node
10
+	// RelatedNodes is an optional list of other nodes that are involved in this marker.
11
+	RelatedNodes []graph.Node
12
+
13
+	// Severity indicates how important this problem is.
14
+	Severity Severity
15
+	// Key is a short string to identify this message
16
+	Key string
17
+	// Message is a human-readable string that describes what is interesting
18
+	Message string
19
+}
20
+
21
+// Severity indicates how important this problem is.
22
+type Severity string
23
+
24
+const (
25
+	// InfoSeverity is interesting
26
+	InfoSeverity Severity = "info"
27
+	// WarningSeverity is probably wrong, but we aren't certain
28
+	WarningSeverity Severity = "warning"
29
+	// ErrorSeverity is definitely wrong, this won't work
30
+	ErrorSeverity Severity = "error"
31
+)
32
+
33
+type Markers []Marker
34
+
35
+// MarkerScanner is a function for analyzing a graph and finding interesting things in it
36
+type MarkerScanner func(g Graph) []Marker
37
+
38
+func (m Markers) BySeverity(severity Severity) []Marker {
39
+	ret := []Marker{}
40
+	for i := range m {
41
+		if m[i].Severity == severity {
42
+			ret = append(ret, m[i])
43
+		}
44
+	}
45
+
46
+	return ret
47
+}
48
+
49
+type BySeverity []Marker
50
+
51
+func (m BySeverity) Len() int      { return len(m) }
52
+func (m BySeverity) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
53
+func (m BySeverity) Less(i, j int) bool {
54
+	lhs := m[i]
55
+	rhs := m[j]
56
+
57
+	switch lhs.Severity {
58
+	case ErrorSeverity:
59
+		switch rhs.Severity {
60
+		case ErrorSeverity:
61
+			return false
62
+		}
63
+	case WarningSeverity:
64
+		switch rhs.Severity {
65
+		case ErrorSeverity, WarningSeverity:
66
+			return false
67
+		}
68
+	case InfoSeverity:
69
+		switch rhs.Severity {
70
+		case ErrorSeverity, WarningSeverity, InfoSeverity:
71
+			return false
72
+		}
73
+	}
74
+
75
+	return true
76
+}
77
+
78
+type ByNodeID []Marker
79
+
80
+func (m ByNodeID) Len() int      { return len(m) }
81
+func (m ByNodeID) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
82
+func (m ByNodeID) Less(i, j int) bool {
83
+	if m[i].Node == nil {
84
+		return true
85
+	}
86
+	return m[i].Node.ID() < m[j].Node.ID()
87
+}
88
+
89
+type ByKey []Marker
90
+
91
+func (m ByKey) Len() int      { return len(m) }
92
+func (m ByKey) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
93
+func (m ByKey) Less(i, j int) bool {
94
+	return m[i].Key < m[j].Key
95
+}
... ...
@@ -1,13 +1,88 @@
1 1
 package analysis
2 2
 
3 3
 import (
4
+	"fmt"
5
+
6
+	"github.com/gonum/graph"
7
+
4 8
 	osgraph "github.com/openshift/origin/pkg/api/graph"
5 9
 	kubeedges "github.com/openshift/origin/pkg/api/kubegraph"
6 10
 	kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
7 11
 )
8 12
 
9
-// CheckMountedSecrets checks to be sure that all the referenced secrets are mountable (by service account) and present (not synthetic)
10
-func CheckMountedSecrets(g osgraph.Graph, podSpecNode *kubegraph.PodSpecNode) ( /*unmountable secrets*/ []*kubegraph.SecretNode /*unresolved secrets*/, []*kubegraph.SecretNode) {
13
+const (
14
+	UnmountableSecretWarning = "UnmountableSecret"
15
+	MissingSecretWarning     = "MissingSecret"
16
+)
17
+
18
+// FindUnmountableSecrets inspects all PodSpecs for any Secret reference that isn't listed as mountable by the referenced ServiceAccount
19
+func FindUnmountableSecrets(g osgraph.Graph) []osgraph.Marker {
20
+	markers := []osgraph.Marker{}
21
+
22
+	for _, uncastPodSpecNode := range g.NodesByKind(kubegraph.PodSpecNodeKind) {
23
+		podSpecNode := uncastPodSpecNode.(*kubegraph.PodSpecNode)
24
+		unmountableSecrets := CheckForUnmountableSecrets(g, podSpecNode)
25
+
26
+		topLevelNode := osgraph.GetTopLevelContainerNode(g, podSpecNode)
27
+		topLevelString := g.Name(topLevelNode)
28
+		if resourceStringer, ok := topLevelNode.(osgraph.ResourceNode); ok {
29
+			topLevelString = resourceStringer.ResourceString()
30
+		}
31
+
32
+		saString := "MISSING_SA"
33
+		saNodes := g.SuccessorNodesByEdgeKind(podSpecNode, kubeedges.ReferencedServiceAccountEdgeKind)
34
+		if len(saNodes) > 0 {
35
+			saString = saNodes[0].(*kubegraph.ServiceAccountNode).ResourceString()
36
+		}
37
+
38
+		for _, unmountableSecret := range unmountableSecrets {
39
+			markers = append(markers, osgraph.Marker{
40
+				Node:         podSpecNode,
41
+				RelatedNodes: []graph.Node{unmountableSecret},
42
+
43
+				Severity: osgraph.WarningSeverity,
44
+				Key:      UnmountableSecretWarning,
45
+				Message: fmt.Sprintf("%s is attempting to mount a secret %s disallowed by %s",
46
+					topLevelString, unmountableSecret.ResourceString(), saString),
47
+			})
48
+		}
49
+	}
50
+
51
+	return markers
52
+}
53
+
54
+// FindMissingSecrets inspects all PodSpecs for any Secret reference that is a synthetic node (not a pre-existing node in the graph)
55
+func FindMissingSecrets(g osgraph.Graph) []osgraph.Marker {
56
+	markers := []osgraph.Marker{}
57
+
58
+	for _, uncastPodSpecNode := range g.NodesByKind(kubegraph.PodSpecNodeKind) {
59
+		podSpecNode := uncastPodSpecNode.(*kubegraph.PodSpecNode)
60
+		missingSecrets := CheckMissingMountedSecrets(g, podSpecNode)
61
+
62
+		topLevelNode := osgraph.GetTopLevelContainerNode(g, podSpecNode)
63
+		topLevelString := g.Name(topLevelNode)
64
+		if resourceStringer, ok := topLevelNode.(osgraph.ResourceNode); ok {
65
+			topLevelString = resourceStringer.ResourceString()
66
+		}
67
+
68
+		for _, missingSecret := range missingSecrets {
69
+			markers = append(markers, osgraph.Marker{
70
+				Node:         podSpecNode,
71
+				RelatedNodes: []graph.Node{missingSecret},
72
+
73
+				Severity: osgraph.WarningSeverity,
74
+				Key:      UnmountableSecretWarning,
75
+				Message: fmt.Sprintf("%s is attempting to mount a missing secret %s",
76
+					topLevelString, missingSecret.ResourceString()),
77
+			})
78
+		}
79
+	}
80
+
81
+	return markers
82
+}
83
+
84
+// CheckForUnmountableSecrets checks to be sure that all the referenced secrets are mountable (by service account)
85
+func CheckForUnmountableSecrets(g osgraph.Graph, podSpecNode *kubegraph.PodSpecNode) []*kubegraph.SecretNode {
11 86
 	saNodes := g.SuccessorNodesByNodeAndEdgeKind(podSpecNode, kubegraph.ServiceAccountNodeKind, kubeedges.ReferencedServiceAccountEdgeKind)
12 87
 	saMountableSecrets := []*kubegraph.SecretNode{}
13 88
 
... ...
@@ -19,13 +94,9 @@ func CheckMountedSecrets(g osgraph.Graph, podSpecNode *kubegraph.PodSpecNode) (
19 19
 	}
20 20
 
21 21
 	unmountableSecrets := []*kubegraph.SecretNode{}
22
-	missingSecrets := []*kubegraph.SecretNode{}
23 22
 
24 23
 	for _, uncastMountedSecretNode := range g.SuccessorNodesByNodeAndEdgeKind(podSpecNode, kubegraph.SecretNodeKind, kubeedges.MountedSecretEdgeKind) {
25 24
 		mountedSecretNode := uncastMountedSecretNode.(*kubegraph.SecretNode)
26
-		if !mountedSecretNode.Found() {
27
-			missingSecrets = append(missingSecrets, mountedSecretNode)
28
-		}
29 25
 
30 26
 		mountable := false
31 27
 		for _, mountableSecretNode := range saMountableSecrets {
... ...
@@ -41,5 +112,19 @@ func CheckMountedSecrets(g osgraph.Graph, podSpecNode *kubegraph.PodSpecNode) (
41 41
 		}
42 42
 	}
43 43
 
44
-	return unmountableSecrets, missingSecrets
44
+	return unmountableSecrets
45
+}
46
+
47
+// CheckMissingMountedSecrets checks to be sure that all the referenced secrets are present (not synthetic)
48
+func CheckMissingMountedSecrets(g osgraph.Graph, podSpecNode *kubegraph.PodSpecNode) []*kubegraph.SecretNode {
49
+	missingSecrets := []*kubegraph.SecretNode{}
50
+
51
+	for _, uncastMountedSecretNode := range g.SuccessorNodesByNodeAndEdgeKind(podSpecNode, kubegraph.SecretNodeKind, kubeedges.MountedSecretEdgeKind) {
52
+		mountedSecretNode := uncastMountedSecretNode.(*kubegraph.SecretNode)
53
+		if !mountedSecretNode.Found() {
54
+			missingSecrets = append(missingSecrets, mountedSecretNode)
55
+		}
56
+	}
57
+
58
+	return missingSecrets
45 59
 }
46 60
new file mode 100644
... ...
@@ -0,0 +1,83 @@
0
+package analysis
1
+
2
+import (
3
+	"testing"
4
+
5
+	osgraph "github.com/openshift/origin/pkg/api/graph"
6
+	osgraphtest "github.com/openshift/origin/pkg/api/graph/test"
7
+	kubeedges "github.com/openshift/origin/pkg/api/kubegraph"
8
+)
9
+
10
+func TestMissingSecrets(t *testing.T) {
11
+	g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/bad_secret_refs.yaml")
12
+	if err != nil {
13
+		t.Fatalf("unexpected error: %v", err)
14
+	}
15
+
16
+	kubeedges.AddAllRequestedServiceAccountEdges(g)
17
+	kubeedges.AddAllMountableSecretEdges(g)
18
+	kubeedges.AddAllMountedSecretEdges(g)
19
+
20
+	markers := FindMissingSecrets(g)
21
+	if e, a := 1, len(markers); e != a {
22
+		t.Fatalf("expected %v, got %v", e, a)
23
+	}
24
+
25
+	actualDC := osgraph.GetTopLevelContainerNode(g, markers[0].Node)
26
+	expectedDC := g.Find(osgraph.UniqueName("DeploymentConfig|/docker-nfs-server"))
27
+	if e, a := expectedDC.ID(), actualDC.ID(); e != a {
28
+		t.Errorf("expected %v, got %v", e, a)
29
+	}
30
+
31
+	actualSecret := markers[0].RelatedNodes[0]
32
+	expectedSecret := g.Find(osgraph.UniqueName("Secret|/missing-secret"))
33
+	if e, a := expectedSecret.ID(), actualSecret.ID(); e != a {
34
+		t.Errorf("expected %v, got %v", e, a)
35
+
36
+	}
37
+}
38
+
39
+func TestUnmountableSecrets(t *testing.T) {
40
+	g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/bad_secret_refs.yaml")
41
+	if err != nil {
42
+		t.Fatalf("unexpected error: %v", err)
43
+	}
44
+
45
+	kubeedges.AddAllRequestedServiceAccountEdges(g)
46
+	kubeedges.AddAllMountableSecretEdges(g)
47
+	kubeedges.AddAllMountedSecretEdges(g)
48
+
49
+	markers := FindUnmountableSecrets(g)
50
+	if e, a := 2, len(markers); e != a {
51
+		t.Errorf("expected %v, got %v", e, a)
52
+	}
53
+
54
+	expectedSecret1 := g.Find(osgraph.UniqueName("Secret|/missing-secret"))
55
+	expectedSecret2 := g.Find(osgraph.UniqueName("Secret|/unmountable-secret"))
56
+	found1 := false
57
+	found2 := false
58
+
59
+	for i := 0; i < 2; i++ {
60
+		actualDC := osgraph.GetTopLevelContainerNode(g, markers[i].Node)
61
+		expectedDC := g.Find(osgraph.UniqueName("DeploymentConfig|/docker-nfs-server"))
62
+		if e, a := expectedDC.ID(), actualDC.ID(); e != a {
63
+			t.Errorf("expected %v, got %v", e, a)
64
+		}
65
+
66
+		actualSecret := markers[i].RelatedNodes[0]
67
+		if e, a := expectedSecret1.ID(), actualSecret.ID(); e == a {
68
+			found1 = true
69
+		}
70
+		if e, a := expectedSecret2.ID(), actualSecret.ID(); e == a {
71
+			found2 = true
72
+		}
73
+	}
74
+
75
+	if !found1 {
76
+		t.Errorf("expected %v, got %v", expectedSecret1, markers)
77
+	}
78
+
79
+	if !found2 {
80
+		t.Errorf("expected %v, got %v", expectedSecret2, markers)
81
+	}
82
+}
0 83
new file mode 100644
... ...
@@ -0,0 +1,85 @@
0
+package analysis
1
+
2
+import (
3
+	"fmt"
4
+	"strings"
5
+
6
+	"github.com/gonum/graph"
7
+	"github.com/gonum/graph/topo"
8
+
9
+	osgraph "github.com/openshift/origin/pkg/api/graph"
10
+	buildedges "github.com/openshift/origin/pkg/build/graph"
11
+	buildgraph "github.com/openshift/origin/pkg/build/graph/nodes"
12
+	imageedges "github.com/openshift/origin/pkg/image/graph"
13
+	imagegraph "github.com/openshift/origin/pkg/image/graph/nodes"
14
+)
15
+
16
+const (
17
+	MissingRequiredRegistryWarning = "MissingRequiredRegistry"
18
+	CyclicBuildConfigWarning       = "CyclicBuildConfig"
19
+)
20
+
21
+// FindUnpushableBuildConfigs checks all build configs that will output to an IST backed by an ImageStream and checks to make sure their builds can push.
22
+func FindUnpushableBuildConfigs(g osgraph.Graph) []osgraph.Marker {
23
+	markers := []osgraph.Marker{}
24
+
25
+bc:
26
+	for _, bcNode := range g.NodesByKind(buildgraph.BuildConfigNodeKind) {
27
+		for _, istNode := range g.SuccessorNodesByEdgeKind(bcNode, buildedges.BuildOutputEdgeKind) {
28
+			for _, uncastImageStreamNode := range g.SuccessorNodesByEdgeKind(istNode, imageedges.ReferencedImageStreamGraphEdgeKind) {
29
+				imageStreamNode := uncastImageStreamNode.(*imagegraph.ImageStreamNode)
30
+
31
+				if len(imageStreamNode.Status.DockerImageRepository) == 0 {
32
+					markers = append(markers, osgraph.Marker{
33
+						Node:         bcNode,
34
+						RelatedNodes: []graph.Node{istNode},
35
+
36
+						Severity: osgraph.WarningSeverity,
37
+						Key:      MissingRequiredRegistryWarning,
38
+						Message: fmt.Sprintf("%s is pushing to %s that is using %s, but the administrator has not configured the integrated Docker registry.  (oadm registry)",
39
+							bcNode.(*buildgraph.BuildConfigNode).ResourceString(), istNode.(*imagegraph.ImageStreamTagNode).ResourceString(), imageStreamNode.ResourceString()),
40
+					})
41
+
42
+					continue bc
43
+				}
44
+			}
45
+		}
46
+	}
47
+
48
+	return markers
49
+}
50
+
51
+// FindCircularBuilds checks all build configs for cycles
52
+func FindCircularBuilds(g osgraph.Graph) []osgraph.Marker {
53
+	// Filter out all but ImageStreamTag and BuildConfig nodes
54
+	nodeFn := osgraph.NodesOfKind(imagegraph.ImageStreamTagNodeKind, buildgraph.BuildConfigNodeKind)
55
+	// Filter out all but BuildInputImage and BuildOutput edges
56
+	edgeFn := osgraph.EdgesOfKind(buildedges.BuildInputImageEdgeKind, buildedges.BuildOutputEdgeKind)
57
+
58
+	// Create desired subgraph
59
+	sub := g.Subgraph(nodeFn, edgeFn)
60
+
61
+	markers := []osgraph.Marker{}
62
+
63
+	// Check for cycles
64
+	for _, cycle := range topo.CyclesIn(sub) {
65
+		nodeNames := []string{}
66
+		for _, node := range cycle {
67
+			if resourceStringer, ok := node.(osgraph.ResourceNode); ok {
68
+				nodeNames = append(nodeNames, resourceStringer.ResourceString())
69
+			}
70
+		}
71
+
72
+		markers = append(markers, osgraph.Marker{
73
+			Node:         cycle[0],
74
+			RelatedNodes: cycle,
75
+
76
+			Severity: osgraph.WarningSeverity,
77
+			Key:      CyclicBuildConfigWarning,
78
+			Message:  fmt.Sprintf("Cycle detected in build configurations: %s", strings.Join(nodeNames, " -> ")),
79
+		})
80
+
81
+	}
82
+
83
+	return markers
84
+}
0 85
new file mode 100644
... ...
@@ -0,0 +1,75 @@
0
+package analysis
1
+
2
+import (
3
+	"testing"
4
+
5
+	osgraph "github.com/openshift/origin/pkg/api/graph"
6
+	osgraphtest "github.com/openshift/origin/pkg/api/graph/test"
7
+	buildedges "github.com/openshift/origin/pkg/build/graph"
8
+	imageedges "github.com/openshift/origin/pkg/image/graph"
9
+)
10
+
11
+func TestUnpushableBuild(t *testing.T) {
12
+	g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/unpushable-build.yaml")
13
+	if err != nil {
14
+		t.Fatalf("unexpected error: %v", err)
15
+	}
16
+
17
+	buildedges.AddAllInputOutputEdges(g)
18
+	imageedges.AddAllImageStreamRefEdges(g)
19
+
20
+	markers := FindUnpushableBuildConfigs(g)
21
+	if e, a := 1, len(markers); e != a {
22
+		t.Fatalf("expected %v, got %v", e, a)
23
+	}
24
+
25
+	actualBC := osgraph.GetTopLevelContainerNode(g, markers[0].Node)
26
+	expectedBC := g.Find(osgraph.UniqueName("BuildConfig|/ruby-hello-world"))
27
+	if e, a := expectedBC.ID(), actualBC.ID(); e != a {
28
+		t.Errorf("expected %v, got %v", e, a)
29
+	}
30
+
31
+	actualIST := markers[0].RelatedNodes[0]
32
+	expectedIST := g.Find(osgraph.UniqueName("ImageStreamTag|/ruby-hello-world:latest"))
33
+	if e, a := expectedIST.ID(), actualIST.ID(); e != a {
34
+		t.Errorf("expected %v, got %v: \n%v", e, a, g)
35
+	}
36
+
37
+}
38
+
39
+func TestPushableBuild(t *testing.T) {
40
+	g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/pushable-build.yaml")
41
+	if err != nil {
42
+		t.Fatalf("unexpected error: %v", err)
43
+	}
44
+
45
+	buildedges.AddAllInputOutputEdges(g)
46
+	imageedges.AddAllImageStreamRefEdges(g)
47
+
48
+	if e, a := 0, len(FindUnpushableBuildConfigs(g)); e != a {
49
+		t.Errorf("expected %v, got %v", e, a)
50
+	}
51
+}
52
+
53
+func TestCircularDeps(t *testing.T) {
54
+	g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/circular.yaml")
55
+	if err != nil {
56
+		t.Fatalf("unexpected error: %v", err)
57
+	}
58
+	buildedges.AddAllInputOutputEdges(g)
59
+
60
+	if len(FindCircularBuilds(g)) != 1 {
61
+		t.Fatalf("expected having circular dependencies")
62
+	}
63
+
64
+	not, _, err := osgraphtest.BuildGraph("../../../api/graph/test/circular-not.yaml")
65
+	if err != nil {
66
+		t.Fatalf("unexpected error: %v", err)
67
+	}
68
+	buildedges.AddAllInputOutputEdges(not)
69
+
70
+	if len(FindCircularBuilds(not)) != 0 {
71
+		t.Fatalf("expected not having circular dependencies")
72
+	}
73
+
74
+}
... ...
@@ -3,6 +3,7 @@ package describe
3 3
 import (
4 4
 	"fmt"
5 5
 	"io"
6
+	"sort"
6 7
 	"strings"
7 8
 	"text/tabwriter"
8 9
 
... ...
@@ -12,7 +13,6 @@ import (
12 12
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
13 13
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
14 14
 	utilerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
15
-	"github.com/gonum/graph/topo"
16 15
 
17 16
 	osgraph "github.com/openshift/origin/pkg/api/graph"
18 17
 	"github.com/openshift/origin/pkg/api/graph/graphview"
... ...
@@ -21,6 +21,7 @@ import (
21 21
 	kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
22 22
 	buildapi "github.com/openshift/origin/pkg/build/api"
23 23
 	buildedges "github.com/openshift/origin/pkg/build/graph"
24
+	buildanalysis "github.com/openshift/origin/pkg/build/graph/analysis"
24 25
 	buildgraph "github.com/openshift/origin/pkg/build/graph/nodes"
25 26
 	"github.com/openshift/origin/pkg/client"
26 27
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
... ...
@@ -161,14 +162,23 @@ func (d *ProjectStatusDescriber) Describe(namespace, name string) (string, error
161 161
 		// always output warnings
162 162
 		fmt.Fprintln(out)
163 163
 
164
-		if hasUnresolvedImageStreamTag(g) {
165
-			fmt.Fprintln(out, "Warning: Some of your builds are pointing to image streams, but the administrator has not configured the integrated Docker registry (oadm registry).")
164
+		allMarkers := osgraph.Markers{}
165
+		for _, scanner := range getMarkerScanners() {
166
+			allMarkers = append(allMarkers, scanner(g)...)
166 167
 		}
167
-		if hasCircularDependencies(g) {
168
-			fmt.Fprintln(out, "Warning: Some of your build configurations have circular dependencies.")
168
+		sort.Stable(osgraph.ByKey(allMarkers))
169
+		sort.Stable(osgraph.ByNodeID(allMarkers))
170
+		if errorMarkers := allMarkers.BySeverity(osgraph.ErrorSeverity); len(errorMarkers) > 0 {
171
+			fmt.Fprintln(out, "Errors:")
172
+			for _, marker := range errorMarkers {
173
+				fmt.Fprintln(out, indent+marker.Message)
174
+			}
169 175
 		}
170
-		if lines, _ := describeBadPodSpecs(out, g); len(lines) > 0 {
171
-			fmt.Fprintln(out, strings.Join(lines, "\n"))
176
+		if warningMarkers := allMarkers.BySeverity(osgraph.WarningSeverity); len(warningMarkers) > 0 {
177
+			fmt.Fprintln(out, "Warnings:")
178
+			for _, marker := range warningMarkers {
179
+				fmt.Fprintln(out, indent+marker.Message)
180
+			}
172 181
 		}
173 182
 
174 183
 		if (len(services) == 0) && (len(standaloneDCs) == 0) && (len(standaloneImages) == 0) {
... ...
@@ -184,77 +194,13 @@ func (d *ProjectStatusDescriber) Describe(namespace, name string) (string, error
184 184
 	})
185 185
 }
186 186
 
187
-// hasUnresolvedImageStreamTag checks all build configs that will output to an IST backed by an ImageStream and checks to make sure their builds can push.
188
-func hasUnresolvedImageStreamTag(g osgraph.Graph) bool {
189
-	for _, bcNode := range g.NodesByKind(buildgraph.BuildConfigNodeKind) {
190
-		for _, istNode := range g.SuccessorNodesByEdgeKind(bcNode, buildedges.BuildOutputEdgeKind) {
191
-			for _, uncastImageStreamNode := range g.SuccessorNodesByEdgeKind(istNode, imageedges.ReferencedImageStreamGraphEdgeKind) {
192
-				imageStreamNode := uncastImageStreamNode.(*imagegraph.ImageStreamNode)
193
-				if len(imageStreamNode.Status.DockerImageRepository) == 0 {
194
-					return true
195
-				}
196
-			}
197
-		}
187
+func getMarkerScanners() []osgraph.MarkerScanner {
188
+	return []osgraph.MarkerScanner{
189
+		kubeanalysis.FindUnmountableSecrets,
190
+		kubeanalysis.FindMissingSecrets,
191
+		buildanalysis.FindUnpushableBuildConfigs,
192
+		buildanalysis.FindCircularBuilds,
198 193
 	}
199
-
200
-	return false
201
-}
202
-
203
-func hasCircularDependencies(g osgraph.Graph) bool {
204
-	// Filter out all but ImageStreamTag and BuildConfig nodes
205
-	nodeFn := osgraph.NodesOfKind(imagegraph.ImageStreamTagNodeKind, buildgraph.BuildConfigNodeKind)
206
-	// Filter out all but BuildInputImage and BuildOutput edges
207
-	edgeFn := osgraph.EdgesOfKind(buildedges.BuildInputImageEdgeKind, buildedges.BuildOutputEdgeKind)
208
-
209
-	// Create desired subgraph
210
-	sub := g.Subgraph(nodeFn, edgeFn)
211
-
212
-	// Check for cycles
213
-	return len(topo.CyclesIn(sub)) != 0
214
-}
215
-
216
-func describeBadPodSpecs(out io.Writer, g osgraph.Graph) ([]string, []*kubegraph.SecretNode) {
217
-	allMissingSecrets := []*kubegraph.SecretNode{}
218
-	lines := []string{}
219
-
220
-	for _, uncastPodSpec := range g.NodesByKind(kubegraph.PodSpecNodeKind) {
221
-		podSpecNode := uncastPodSpec.(*kubegraph.PodSpecNode)
222
-		unmountableSecrets, missingSecrets := kubeanalysis.CheckMountedSecrets(g, podSpecNode)
223
-		containingNode := osgraph.GetTopLevelContainerNode(g, podSpecNode)
224
-
225
-		allMissingSecrets = append(allMissingSecrets, missingSecrets...)
226
-
227
-		unmountableNames := []string{}
228
-		for _, secret := range unmountableSecrets {
229
-			unmountableNames = append(unmountableNames, secret.ResourceString())
230
-		}
231
-
232
-		missingNames := []string{}
233
-		for _, secret := range missingSecrets {
234
-			missingNames = append(missingNames, secret.ResourceString())
235
-		}
236
-
237
-		containingNodeName := g.GraphDescriber.Name(containingNode)
238
-		if resourceNode, ok := containingNode.(osgraph.ResourceNode); ok {
239
-			containingNodeName = resourceNode.ResourceString()
240
-		}
241
-
242
-		switch {
243
-		case len(unmountableSecrets) > 0 && len(missingSecrets) > 0:
244
-			lines = append(lines, fmt.Sprintf("\t%s is not allowed to mount %s and wants to mount these missing secrets %s", containingNodeName, strings.Join(unmountableNames, ","), strings.Join(missingNames, ",")))
245
-		case len(unmountableSecrets) > 0:
246
-			lines = append(lines, fmt.Sprintf("\t%s is not allowed to mount %s", containingNodeName, strings.Join(unmountableNames, ",")))
247
-		case len(unmountableSecrets) > 0 && len(missingSecrets) > 0:
248
-			lines = append(lines, fmt.Sprintf("\t%s wants to mount these missing secrets %s", containingNodeName, strings.Join(missingNames, ",")))
249
-		}
250
-	}
251
-
252
-	// if we had any failures, prepend the warning line
253
-	if len(lines) > 0 {
254
-		return append([]string{"Warning: some requested secrets are not allowed:"}, lines...), allMissingSecrets
255
-	}
256
-
257
-	return []string{}, allMissingSecrets
258 194
 }
259 195
 
260 196
 func printLines(out io.Writer, indent string, depth int, lines ...string) {
... ...
@@ -10,10 +10,7 @@ import (
10 10
 	ktestclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client/testclient"
11 11
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
12 12
 
13
-	osgraphtest "github.com/openshift/origin/pkg/api/graph/test"
14
-	buildedges "github.com/openshift/origin/pkg/build/graph"
15 13
 	"github.com/openshift/origin/pkg/client/testclient"
16
-	imageedges "github.com/openshift/origin/pkg/image/graph"
17 14
 	projectapi "github.com/openshift/origin/pkg/project/api"
18 15
 )
19 16
 
... ...
@@ -25,57 +22,6 @@ func mustParseTime(t string) time.Time {
25 25
 	return out
26 26
 }
27 27
 
28
-func TestUnpushableBuild(t *testing.T) {
29
-	g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/unpushable-build.yaml")
30
-	if err != nil {
31
-		t.Fatalf("unexpected error: %v", err)
32
-	}
33
-
34
-	buildedges.AddAllInputOutputEdges(g)
35
-	imageedges.AddAllImageStreamRefEdges(g)
36
-
37
-	if e, a := true, hasUnresolvedImageStreamTag(g); e != a {
38
-		t.Errorf("expected %v, got %v", e, a)
39
-	}
40
-}
41
-
42
-func TestPushableBuild(t *testing.T) {
43
-	g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/pushable-build.yaml")
44
-	if err != nil {
45
-		t.Fatalf("unexpected error: %v", err)
46
-	}
47
-
48
-	buildedges.AddAllInputOutputEdges(g)
49
-	imageedges.AddAllImageStreamRefEdges(g)
50
-
51
-	if e, a := false, hasUnresolvedImageStreamTag(g); e != a {
52
-		t.Errorf("expected %v, got %v", e, a)
53
-	}
54
-}
55
-
56
-func TestCircularDeps(t *testing.T) {
57
-	g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/circular.yaml")
58
-	if err != nil {
59
-		t.Fatalf("unexpected error: %v", err)
60
-	}
61
-	buildedges.AddAllInputOutputEdges(g)
62
-
63
-	if !hasCircularDependencies(g) {
64
-		t.Fatalf("expected having circular dependencies")
65
-	}
66
-
67
-	not, _, err := osgraphtest.BuildGraph("../../../api/graph/test/circular-not.yaml")
68
-	if err != nil {
69
-		t.Fatalf("unexpected error: %v", err)
70
-	}
71
-	buildedges.AddAllInputOutputEdges(not)
72
-
73
-	if hasCircularDependencies(not) {
74
-		t.Fatalf("expected not having circular dependencies")
75
-	}
76
-
77
-}
78
-
79 28
 func TestProjectStatus(t *testing.T) {
80 29
 	testCases := map[string]struct {
81 30
 		Path     string
... ...
@@ -148,8 +94,9 @@ func TestProjectStatus(t *testing.T) {
148 148
 				"In project example\n",
149 149
 				"rc/my-rc runs openshift/mysql-55-centos7",
150 150
 				"0/1 pods growing to 1",
151
-				"is not allowed to mount secret/existing-secret",
152
-				"these missing secrets secret/dne",
151
+				"rc/my-rc is attempting to mount a secret secret/existing-secret disallowed by sa/default",
152
+				"rc/my-rc is attempting to mount a secret secret/dne disallowed by sa/default",
153
+				"rc/my-rc is attempting to mount a missing secret secret/dne",
153 154
 			},
154 155
 		},
155 156
 		"service with pod": {
... ...
@@ -199,6 +146,30 @@ func TestProjectStatus(t *testing.T) {
199 199
 				"To see more, use",
200 200
 			},
201 201
 		},
202
+		"unpushable build": {
203
+			Path: "../../../../pkg/api/graph/test/unpushable-build.yaml",
204
+			Extra: []runtime.Object{
205
+				&projectapi.Project{
206
+					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
207
+				},
208
+			},
209
+			ErrFn: func(err error) bool { return err == nil },
210
+			Contains: []string{
211
+				"bc/ruby-hello-world is pushing to imagestreamtag/ruby-hello-world:latest that is using is/ruby-hello-world, but the administrator has not configured the integrated Docker registry.",
212
+			},
213
+		},
214
+		"cyclical build": {
215
+			Path: "../../../../pkg/api/graph/test/circular.yaml",
216
+			Extra: []runtime.Object{
217
+				&projectapi.Project{
218
+					ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""},
219
+				},
220
+			},
221
+			ErrFn: func(err error) bool { return err == nil },
222
+			Contains: []string{
223
+				"Cycle detected in build configurations:",
224
+			},
225
+		},
202 226
 		"running build": {
203 227
 			Path: "../../../../test/fixtures/app-scenarios/new-project-one-build.yaml",
204 228
 			Extra: []runtime.Object{
... ...
@@ -294,6 +265,5 @@ func TestProjectStatus(t *testing.T) {
294 294
 				t.Errorf("%s: did not have %q:\n%s\n---", k, s, out)
295 295
 			}
296 296
 		}
297
-		t.Logf("\n%s", out)
298 297
 	}
299 298
 }
300 299
deleted file mode 100644
... ...
@@ -1,54 +0,0 @@
1
-package analysis
2
-
3
-import (
4
-	"github.com/gonum/graph"
5
-
6
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
7
-
8
-	osgraph "github.com/openshift/origin/pkg/api/graph"
9
-	"github.com/openshift/origin/pkg/api/graph/graphview"
10
-	kubeanalysis "github.com/openshift/origin/pkg/api/kubegraph/analysis"
11
-	kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
12
-	deploygraph "github.com/openshift/origin/pkg/deploy/graph/nodes"
13
-)
14
-
15
-// DescendentNodesByNodeKind starts at the root navigates down the root.  Every edge is checked against the edgeChecker
16
-// to determine whether or not to follow it.  The nodes at the tail end of every chased edge are then checked against the
17
-// the targetNodeKind.  Matches are added to the return and every checked node then has its edges checked: lather, rinse, repeat
18
-func DescendentNodesByNodeKind(g osgraph.Graph, visitedNodes graphview.IntSet, node graph.Node, targetNodeKind string, edgeChecker osgraph.EdgeFunc) []graph.Node {
19
-	if visitedNodes.Has(node.ID()) {
20
-		return []graph.Node{}
21
-	}
22
-	visitedNodes.Insert(node.ID())
23
-
24
-	ret := []graph.Node{}
25
-	for _, successor := range g.From(node) {
26
-		edge := g.Edge(node, successor)
27
-
28
-		if edgeChecker(osgraph.New(), node, successor, g.EdgeKinds(edge)) {
29
-			if g.Kind(successor) == targetNodeKind {
30
-				ret = append(ret, successor)
31
-			}
32
-
33
-			ret = append(ret, DescendentNodesByNodeKind(g, visitedNodes, successor, targetNodeKind, edgeChecker)...)
34
-		}
35
-	}
36
-
37
-	return ret
38
-}
39
-
40
-// CheckMountedSecrets checks to be sure that all the referenced secrets are mountable (by service account) and present (not synthetic)
41
-func CheckMountedSecrets(g osgraph.Graph, dcNode *deploygraph.DeploymentConfigNode) ( /*unmountable secrets*/ []*kubegraph.SecretNode /*unresolved secrets*/, []*kubegraph.SecretNode) {
42
-	podSpecs := DescendentNodesByNodeKind(g, graphview.IntSet{}, dcNode, kubegraph.PodSpecNodeKind, func(g osgraph.Interface, head, tail graph.Node, edgeKinds util.StringSet) bool {
43
-		if edgeKinds.Has(osgraph.ContainsEdgeKind) {
44
-			return true
45
-		}
46
-		return false
47
-	})
48
-
49
-	if len(podSpecs) > 0 {
50
-		return kubeanalysis.CheckMountedSecrets(g, podSpecs[0].(*kubegraph.PodSpecNode))
51
-	}
52
-
53
-	return []*kubegraph.SecretNode{}, []*kubegraph.SecretNode{}
54
-}
55 1
deleted file mode 100644
... ...
@@ -1,45 +0,0 @@
1
-package analysis
2
-
3
-import (
4
-	"testing"
5
-
6
-	osgraphtest "github.com/openshift/origin/pkg/api/graph/test"
7
-	kubeedges "github.com/openshift/origin/pkg/api/kubegraph"
8
-	deployapi "github.com/openshift/origin/pkg/deploy/api"
9
-	deploygraph "github.com/openshift/origin/pkg/deploy/graph/nodes"
10
-)
11
-
12
-func TestCheckMountedSecrets(t *testing.T) {
13
-	g, objs, err := osgraphtest.BuildGraph("../../../api/graph/test/bad_secret_refs.yaml")
14
-	if err != nil {
15
-		t.Fatalf("unexpected error: %v", err)
16
-	}
17
-
18
-	var dc *deployapi.DeploymentConfig
19
-	for _, obj := range objs {
20
-		if currDC, ok := obj.(*deployapi.DeploymentConfig); ok {
21
-			if dc != nil {
22
-				t.Errorf("got more than one dc: %v", currDC)
23
-			}
24
-			dc = currDC
25
-		}
26
-	}
27
-
28
-	kubeedges.AddAllRequestedServiceAccountEdges(g)
29
-	kubeedges.AddAllMountableSecretEdges(g)
30
-	kubeedges.AddAllMountedSecretEdges(g)
31
-
32
-	dcNode := g.Find(deploygraph.DeploymentConfigNodeName(dc))
33
-	unmountable, missing := CheckMountedSecrets(g, dcNode.(*deploygraph.DeploymentConfigNode))
34
-
35
-	if e, a := 2, len(unmountable); e != a {
36
-		t.Fatalf("expected %v, got %v", e, a)
37
-	}
38
-
39
-	if e, a := 1, len(missing); e != a {
40
-		t.Fatalf("expected %v, got %v", e, a)
41
-	}
42
-	if e, a := "missing-secret", missing[0].Name; e != a {
43
-		t.Fatalf("expected %v, got %v", e, a)
44
-	}
45
-}