Browse code

status: Report routes that have no route port specified

This commit adds graph pieces for routes and makes `oc status` report routes with missing route ports.

kargakis authored on 2015/10/13 02:06:52
Showing 19 changed files
... ...
@@ -28,7 +28,7 @@ func (n Node) DOTAttributes() []dot.Attribute {
28 28
 // The graph needs something in that location to track the information we have about the node, but the
29 29
 // backing object doesn't exist.
30 30
 type ExistenceChecker interface {
31
-	// Found returns true if the node represents an object that we don't have the backing object for
31
+	// Found returns false if the node represents an object that we don't have the backing object for
32 32
 	Found() bool
33 33
 }
34 34
 
... ...
@@ -11,6 +11,8 @@ import (
11 11
 	kubeedges "github.com/openshift/origin/pkg/api/kubegraph"
12 12
 	kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
13 13
 	deploygraph "github.com/openshift/origin/pkg/deploy/graph/nodes"
14
+	routeedges "github.com/openshift/origin/pkg/route/graph"
15
+	routegraph "github.com/openshift/origin/pkg/route/graph/nodes"
14 16
 )
15 17
 
16 18
 // ServiceGroup is a service, the DeploymentConfigPipelines it covers, and lists of the other nodes that fulfill it
... ...
@@ -23,6 +25,8 @@ type ServiceGroup struct {
23 23
 	FulfillingDCs  []*deploygraph.DeploymentConfigNode
24 24
 	FulfillingRCs  []*kubegraph.ReplicationControllerNode
25 25
 	FulfillingPods []*kubegraph.PodNode
26
+
27
+	ExposingRoutes []*routegraph.RouteNode
26 28
 }
27 29
 
28 30
 // AllServiceGroups returns all the ServiceGroups that aren't in the excludes set and the set of covered NodeIDs
... ...
@@ -40,11 +44,11 @@ func AllServiceGroups(g osgraph.Graph, excludeNodeIDs IntSet) ([]ServiceGroup, I
40 40
 		services = append(services, service)
41 41
 	}
42 42
 
43
-	sort.Sort(SortedServiceGroups(services))
43
+	sort.Sort(ServiceGroupByObjectMeta(services))
44 44
 	return services, covered
45 45
 }
46 46
 
47
-// NewServiceGroup returns the ServiceGroup and a set of all the NodeIDs covered by the service service
47
+// NewServiceGroup returns the ServiceGroup and a set of all the NodeIDs covered by the service
48 48
 func NewServiceGroup(g osgraph.Graph, serviceNode *kubegraph.ServiceNode) (ServiceGroup, IntSet) {
49 49
 	covered := IntSet{}
50 50
 	covered.Insert(serviceNode.ID())
... ...
@@ -62,7 +66,17 @@ func NewServiceGroup(g osgraph.Graph, serviceNode *kubegraph.ServiceNode) (Servi
62 62
 			service.FulfillingRCs = append(service.FulfillingRCs, castContainer)
63 63
 		case *kubegraph.PodNode:
64 64
 			service.FulfillingPods = append(service.FulfillingPods, castContainer)
65
+		default:
66
+			util.HandleError(fmt.Errorf("unrecognized container: %v", castContainer))
67
+		}
68
+	}
69
+
70
+	for _, uncastServiceFulfiller := range g.PredecessorNodesByEdgeKind(serviceNode, routeedges.ExposedThroughRouteEdgeKind) {
71
+		container := osgraph.GetTopLevelContainerNode(g, uncastServiceFulfiller)
65 72
 
73
+		switch castContainer := container.(type) {
74
+		case *routegraph.RouteNode:
75
+			service.ExposingRoutes = append(service.ExposingRoutes, castContainer)
66 76
 		default:
67 77
 			util.HandleError(fmt.Errorf("unrecognized container: %v", castContainer))
68 78
 		}
... ...
@@ -86,11 +100,11 @@ func NewServiceGroup(g osgraph.Graph, serviceNode *kubegraph.ServiceNode) (Servi
86 86
 	return service, covered
87 87
 }
88 88
 
89
-type SortedServiceGroups []ServiceGroup
89
+type ServiceGroupByObjectMeta []ServiceGroup
90 90
 
91
-func (m SortedServiceGroups) Len() int      { return len(m) }
92
-func (m SortedServiceGroups) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
93
-func (m SortedServiceGroups) Less(i, j int) bool {
91
+func (m ServiceGroupByObjectMeta) Len() int      { return len(m) }
92
+func (m ServiceGroupByObjectMeta) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
93
+func (m ServiceGroupByObjectMeta) Less(i, j int) bool {
94 94
 	a, b := m[i], m[j]
95 95
 	return CompareObjectMeta(&a.Service.Service.ObjectMeta, &b.Service.Service.ObjectMeta)
96 96
 }
97 97
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+apiVersion: v1
1
+kind: Route
2
+metadata:
3
+  creationTimestamp: 2015-10-13T10:13:11Z
4
+  labels:
5
+    route: lonely
6
+  name: lonely-route
7
+  resourceVersion: "260"
8
+  uid: 025407f7-7193-11e5-b84d-080027c5bfa9
9
+spec:
10
+  host: www.example.com
11
+  to:
12
+    kind: Service
13
+    name: frontend
14
+status: {}
0 15
new file mode 100644
... ...
@@ -0,0 +1,47 @@
0
+apiVersion: v1
1
+kind: List
2
+items:
3
+- apiVersion: v1
4
+  kind: Service
5
+  metadata:
6
+    creationTimestamp: 2015-10-13T10:13:11Z
7
+    labels:
8
+      test: missing-route-port
9
+    name: frontend
10
+    resourceVersion: "259"
11
+    selfLink: /api/v1/namespaces/test/services/frontend
12
+    uid: 024d82eb-7193-11e5-b84d-080027c5bfa9
13
+  spec:
14
+    clusterIP: 172.30.182.32
15
+    portalIP: 172.30.182.32
16
+    ports:
17
+    - name: web
18
+      port: 5432
19
+      protocol: TCP
20
+      targetPort: 8080
21
+    - name: web2
22
+      port: 5433
23
+      protocol: TCP
24
+      targetPort: 8080
25
+    selector:
26
+      name: frontend
27
+    sessionAffinity: None
28
+    type: ClusterIP
29
+  status:
30
+    loadBalancer: {}
31
+- apiVersion: v1
32
+  kind: Route
33
+  metadata:
34
+    creationTimestamp: 2015-10-13T10:13:11Z
35
+    labels:
36
+      test: missing-route-port
37
+    name: route-edge
38
+    resourceVersion: "260"
39
+    selfLink: /oapi/v1/namespaces/test/routes/route-edge
40
+    uid: 025407f7-7193-11e5-b84d-080027c5bfa9
41
+  spec:
42
+    host: www.example.com
43
+    to:
44
+      kind: Service
45
+      name: frontend
46
+  status: {}
... ...
@@ -19,6 +19,8 @@ import (
19 19
 	deploygraph "github.com/openshift/origin/pkg/deploy/graph/nodes"
20 20
 	imageapi "github.com/openshift/origin/pkg/image/api"
21 21
 	imagegraph "github.com/openshift/origin/pkg/image/graph/nodes"
22
+	routeapi "github.com/openshift/origin/pkg/route/api"
23
+	routegraph "github.com/openshift/origin/pkg/route/graph/nodes"
22 24
 )
23 25
 
24 26
 // typeToEnsureMethod stores types to Ensure*Node methods
... ...
@@ -55,6 +57,9 @@ func init() {
55 55
 	if err := RegisterEnsureNode(&kapi.ReplicationController{}, kubegraph.EnsureReplicationControllerNode); err != nil {
56 56
 		panic(err)
57 57
 	}
58
+	if err := RegisterEnsureNode(&routeapi.Route{}, routegraph.EnsureRouteNode); err != nil {
59
+		panic(err)
60
+	}
58 61
 }
59 62
 
60 63
 func RegisterEnsureNode(containedType, ensureFunction interface{}) error {
... ...
@@ -37,7 +37,17 @@ func EnsureServiceNode(g osgraph.MutableUniqueGraph, svc *kapi.Service) *Service
37 37
 	return osgraph.EnsureUnique(g,
38 38
 		ServiceNodeName(svc),
39 39
 		func(node osgraph.Node) graph.Node {
40
-			return &ServiceNode{node, svc}
40
+			return &ServiceNode{node, svc, true}
41
+		},
42
+	).(*ServiceNode)
43
+}
44
+
45
+// FindOrCreateSyntheticServiceNode returns the existing service node or creates a synthetic node in its place
46
+func FindOrCreateSyntheticServiceNode(g osgraph.MutableUniqueGraph, svc *kapi.Service) *ServiceNode {
47
+	return osgraph.EnsureUnique(g,
48
+		ServiceNodeName(svc),
49
+		func(node osgraph.Node) graph.Node {
50
+			return &ServiceNode{node, svc, false}
41 51
 		},
42 52
 	).(*ServiceNode)
43 53
 }
... ...
@@ -27,6 +27,8 @@ func ServiceNodeName(o *kapi.Service) osgraph.UniqueName {
27 27
 type ServiceNode struct {
28 28
 	osgraph.Node
29 29
 	*kapi.Service
30
+
31
+	IsFound bool
30 32
 }
31 33
 
32 34
 func (n ServiceNode) Object() interface{} {
... ...
@@ -45,6 +47,10 @@ func (*ServiceNode) Kind() string {
45 45
 	return ServiceNodeKind
46 46
 }
47 47
 
48
+func (n ServiceNode) Found() bool {
49
+	return n.IsFound
50
+}
51
+
48 52
 func PodNodeName(o *kapi.Pod) osgraph.UniqueName {
49 53
 	return osgraph.GetUniqueRuntimeObjectNodeName(PodNodeKind, o)
50 54
 }
... ...
@@ -35,6 +35,10 @@ import (
35 35
 	imageedges "github.com/openshift/origin/pkg/image/graph"
36 36
 	imagegraph "github.com/openshift/origin/pkg/image/graph/nodes"
37 37
 	projectapi "github.com/openshift/origin/pkg/project/api"
38
+	routeapi "github.com/openshift/origin/pkg/route/api"
39
+	routeedges "github.com/openshift/origin/pkg/route/graph"
40
+	routeanalysis "github.com/openshift/origin/pkg/route/graph/analysis"
41
+	routegraph "github.com/openshift/origin/pkg/route/graph/nodes"
38 42
 	"github.com/openshift/origin/pkg/util/errors"
39 43
 	"github.com/openshift/origin/pkg/util/parallel"
40 44
 )
... ...
@@ -63,6 +67,7 @@ func (d *ProjectStatusDescriber) MakeGraph(namespace string) (osgraph.Graph, set
63 63
 		&buildLoader{namespace: namespace, lister: d.C},
64 64
 		&isLoader{namespace: namespace, lister: d.C},
65 65
 		&dcLoader{namespace: namespace, lister: d.C},
66
+		&routeLoader{namespace: namespace, lister: d.C},
66 67
 	}
67 68
 	loadingFuncs := []func() error{}
68 69
 	for _, loader := range loaders {
... ...
@@ -103,6 +108,7 @@ func (d *ProjectStatusDescriber) MakeGraph(namespace string) (osgraph.Graph, set
103 103
 	deployedges.AddAllTriggerEdges(g)
104 104
 	deployedges.AddAllDeploymentEdges(g)
105 105
 	imageedges.AddAllImageStreamRefEdges(g)
106
+	routeedges.AddAllRouteEdges(g)
106 107
 
107 108
 	return g, forbiddenResources, nil
108 109
 }
... ...
@@ -138,6 +144,10 @@ func (d *ProjectStatusDescriber) Describe(namespace, name string) (string, error
138 138
 		fmt.Fprintf(out, describeProjectAndServer(project, d.Server))
139 139
 
140 140
 		for _, service := range services {
141
+			if !service.Service.Found() {
142
+				continue
143
+			}
144
+
141 145
 			fmt.Fprintln(out)
142 146
 			printLines(out, indent, 0, describeServiceInServiceGroup(service)...)
143 147
 
... ...
@@ -165,6 +175,10 @@ func (d *ProjectStatusDescriber) Describe(namespace, name string) (string, error
165 165
 				}
166 166
 				printLines(out, indent, 1, describePodInServiceGroup(podNode)...)
167 167
 			}
168
+
169
+			for _, routeNode := range service.ExposingRoutes {
170
+				printLines(out, indent, 1, describeRouteInServiceGroup(routeNode)...)
171
+			}
168 172
 		}
169 173
 
170 174
 		for _, standaloneDC := range standaloneDCs {
... ...
@@ -242,6 +256,7 @@ func getMarkerScanners() []osgraph.MarkerScanner {
242 242
 		kubeanalysis.FindMissingSecrets,
243 243
 		buildanalysis.FindUnpushableBuildConfigs,
244 244
 		buildanalysis.FindCircularBuilds,
245
+		routeanalysis.FindMissingPortMapping,
245 246
 	}
246 247
 }
247 248
 
... ...
@@ -275,7 +290,7 @@ func describeProjectAndServer(project *projectapi.Project, server string) string
275 275
 func describeDeploymentInServiceGroup(deploy graphview.DeploymentConfigPipeline) []string {
276 276
 	includeLastPass := deploy.ActiveDeployment == nil
277 277
 	if len(deploy.Images) == 1 {
278
-		lines := []string{fmt.Sprintf("dc/%s deploys %s %s", deploy.Deployment.Name, describeImageInPipeline(deploy.Images[0], deploy.Deployment.Namespace), describeDeploymentConfigTrigger(deploy.Deployment.DeploymentConfig))}
278
+		lines := []string{fmt.Sprintf("%s deploys %s %s", deploy.Deployment.ResourceString(), describeImageInPipeline(deploy.Images[0], deploy.Deployment.Namespace), describeDeploymentConfigTrigger(deploy.Deployment.DeploymentConfig))}
279 279
 		if len(lines[0]) > 120 && strings.Contains(lines[0], " <- ") {
280 280
 			segments := strings.SplitN(lines[0], " <- ", 2)
281 281
 			lines[0] = segments[0] + " <-"
... ...
@@ -286,7 +301,7 @@ func describeDeploymentInServiceGroup(deploy graphview.DeploymentConfigPipeline)
286 286
 		return lines
287 287
 	}
288 288
 
289
-	lines := []string{fmt.Sprintf("dc/%s deploys: %s", deploy.Deployment.Name, describeDeploymentConfigTrigger(deploy.Deployment.DeploymentConfig))}
289
+	lines := []string{fmt.Sprintf("%s deploys: %s", deploy.Deployment.ResourceString(), describeDeploymentConfigTrigger(deploy.Deployment.DeploymentConfig))}
290 290
 	for _, image := range deploy.Images {
291 291
 		lines = append(lines, describeImageInPipeline(image, deploy.Deployment.Namespace))
292 292
 		lines = append(lines, indentLines("  ", describeAdditionalBuildDetail(image.Build, image.LastSuccessfulBuild, image.LastUnsuccessfulBuild, image.ActiveBuilds, image.DestinationResolved, includeLastPass)...)...)
... ...
@@ -305,7 +320,7 @@ func describeRCInServiceGroup(rcNode *kubegraph.ReplicationControllerNode) []str
305 305
 		images = append(images, container.Image)
306 306
 	}
307 307
 
308
-	lines := []string{fmt.Sprintf("rc/%s runs %s", rcNode.ReplicationController.Name, strings.Join(images, ", "))}
308
+	lines := []string{fmt.Sprintf("%s runs %s", rcNode.ResourceString(), strings.Join(images, ", "))}
309 309
 	lines = append(lines, describeRCStatus(rcNode.ReplicationController))
310 310
 
311 311
 	return lines
... ...
@@ -317,10 +332,17 @@ func describePodInServiceGroup(podNode *kubegraph.PodNode) []string {
317 317
 		images = append(images, container.Image)
318 318
 	}
319 319
 
320
-	lines := []string{fmt.Sprintf("pod/%s runs %s", podNode.Pod.Name, strings.Join(images, ", "))}
320
+	lines := []string{fmt.Sprintf("%s runs %s", podNode.ResourceString(), strings.Join(images, ", "))}
321 321
 	return lines
322 322
 }
323 323
 
324
+func describeRouteInServiceGroup(routeNode *routegraph.RouteNode) []string {
325
+	if routeNode.Spec.Port != nil && len(routeNode.Spec.Port.TargetPort.String()) > 0 {
326
+		return []string{fmt.Sprintf("exposed by %s on pod port %s", routeNode.ResourceString(), routeNode.Spec.Port.TargetPort.String())}
327
+	}
328
+	return []string{fmt.Sprintf("exposed by %s", routeNode.ResourceString())}
329
+}
330
+
324 331
 func describeDeploymentConfigTrigger(dc *deployapi.DeploymentConfig) string {
325 332
 	if len(dc.Triggers) == 0 {
326 333
 		return "(manual)"
... ...
@@ -332,7 +354,7 @@ func describeDeploymentConfigTrigger(dc *deployapi.DeploymentConfig) string {
332 332
 func describeStandaloneBuildGroup(pipeline graphview.ImagePipeline, namespace string) []string {
333 333
 	switch {
334 334
 	case pipeline.Build != nil:
335
-		lines := []string{fmt.Sprintf("bc/%s %s", pipeline.Build.BuildConfig.Name, describeBuildInPipeline(pipeline.Build.BuildConfig, pipeline.BaseImage))}
335
+		lines := []string{fmt.Sprintf("%s %s", pipeline.Build.ResourceString(), describeBuildInPipeline(pipeline.Build.BuildConfig, pipeline.BaseImage))}
336 336
 		if pipeline.Image != nil {
337 337
 			lines = append(lines, fmt.Sprintf("pushes to %s", describeImageTagInPipeline(pipeline.Image, namespace)))
338 338
 		}
... ...
@@ -363,7 +385,7 @@ func describeImageTagInPipeline(image graphview.ImageTagLocation, namespace stri
363 363
 		if t.ImageStreamTag.Namespace != namespace {
364 364
 			return image.ImageSpec()
365 365
 		}
366
-		return "istag/" + t.ImageStreamTag.Name
366
+		return t.ResourceString()
367 367
 	default:
368 368
 		return image.ImageSpec()
369 369
 	}
... ...
@@ -672,11 +694,11 @@ func describeServiceInServiceGroup(svc graphview.ServiceGroup) []string {
672 672
 	port := describeServicePorts(spec)
673 673
 	switch {
674 674
 	case ip == "None":
675
-		return []string{fmt.Sprintf("service/%s (headless)%s", svc.Service.Name, port)}
675
+		return []string{fmt.Sprintf("%s (headless)%s", svc.Service.ResourceString(), port)}
676 676
 	case len(ip) == 0:
677
-		return []string{fmt.Sprintf("service/%s <initializing>%s", svc.Service.Name, port)}
677
+		return []string{fmt.Sprintf("%s <initializing>%s", svc.Service.ResourceString(), port)}
678 678
 	default:
679
-		return []string{fmt.Sprintf("service/%s - %s%s", svc.Service.Name, ip, port)}
679
+		return []string{fmt.Sprintf("%s - %s%s", svc.Service.ResourceString(), ip, port)}
680 680
 	}
681 681
 }
682 682
 
... ...
@@ -932,3 +954,27 @@ func (l *buildLoader) AddToGraph(g osgraph.Graph) error {
932 932
 
933 933
 	return nil
934 934
 }
935
+
936
+type routeLoader struct {
937
+	namespace string
938
+	lister    client.RoutesNamespacer
939
+	items     []routeapi.Route
940
+}
941
+
942
+func (l *routeLoader) Load() error {
943
+	list, err := l.lister.Routes(l.namespace).List(labels.Everything(), fields.Everything())
944
+	if err != nil {
945
+		return err
946
+	}
947
+
948
+	l.items = list.Items
949
+	return nil
950
+}
951
+
952
+func (l *routeLoader) AddToGraph(g osgraph.Graph) error {
953
+	for i := range l.items {
954
+		routegraph.EnsureRouteNode(g, &l.items[i])
955
+	}
956
+
957
+	return nil
958
+}
... ...
@@ -61,7 +61,7 @@ func TestProjectStatus(t *testing.T) {
61 61
 			ErrFn: func(err error) bool { return err == nil },
62 62
 			Contains: []string{
63 63
 				"In project example on server https://example.com:8443\n",
64
-				"service/empty-service",
64
+				"svc/empty-service",
65 65
 				"<initializing>:5432",
66 66
 				"To see more, use",
67 67
 			},
... ...
@@ -76,7 +76,7 @@ func TestProjectStatus(t *testing.T) {
76 76
 			ErrFn: func(err error) bool { return err == nil },
77 77
 			Contains: []string{
78 78
 				"In project example on server https://example.com:8443\n",
79
-				"service/database-rc",
79
+				"svc/database-rc",
80 80
 				"rc/database-rc-1 runs mysql",
81 81
 				"0/1 pods growing to 1",
82 82
 				"To see more, use",
... ...
@@ -122,7 +122,7 @@ func TestProjectStatus(t *testing.T) {
122 122
 			ErrFn: func(err error) bool { return err == nil },
123 123
 			Contains: []string{
124 124
 				"In project example on server https://example.com:8443\n",
125
-				"service/frontend-app",
125
+				"svc/frontend-app",
126 126
 				"pod/frontend-app-1-bjwh8 runs openshift/ruby-hello-world",
127 127
 				"To see more, use",
128 128
 			},
... ...
@@ -151,7 +151,7 @@ func TestProjectStatus(t *testing.T) {
151 151
 			ErrFn: func(err error) bool { return err == nil },
152 152
 			Contains: []string{
153 153
 				"In project example on server https://example.com:8443\n",
154
-				"service/sinatra-example-2 - 172.30.17.48:8080",
154
+				"svc/sinatra-example-2 - 172.30.17.48:8080",
155 155
 				"builds git://github.com",
156 156
 				"with docker.io/openshift/ruby-20-centos7:latest",
157 157
 				"not built yet",
... ...
@@ -193,7 +193,7 @@ func TestProjectStatus(t *testing.T) {
193 193
 			ErrFn: func(err error) bool { return err == nil },
194 194
 			Contains: []string{
195 195
 				"In project example on server https://example.com:8443\n",
196
-				"service/sinatra-example-1 - 172.30.17.47:8080",
196
+				"svc/sinatra-example-1 - 172.30.17.47:8080",
197 197
 				"builds git://github.com",
198 198
 				"with docker.io/openshift/ruby-20-centos7:latest",
199 199
 				"#1 build running for about a minute",
... ...
@@ -212,7 +212,7 @@ func TestProjectStatus(t *testing.T) {
212 212
 			ErrFn: func(err error) bool { return err == nil },
213 213
 			Contains: []string{
214 214
 				"In project example on server https://example.com:8443\n",
215
-				"service/sinatra-app-example - 172.30.17.49:8080",
215
+				"svc/sinatra-app-example - 172.30.17.49:8080",
216 216
 				"sinatra-app-example-a deploys",
217 217
 				"sinatra-app-example-b deploys",
218 218
 				"with docker.io/openshift/ruby-20-centos7:latest",
... ...
@@ -232,8 +232,9 @@ func TestProjectStatus(t *testing.T) {
232 232
 			ErrFn: func(err error) bool { return err == nil },
233 233
 			Contains: []string{
234 234
 				"In project example on server https://example.com:8443\n",
235
-				"service/database - 172.30.17.240:5434 -> 3306",
236
-				"service/frontend - 172.30.17.154:5432 -> 8080",
235
+				"svc/database - 172.30.17.240:5434 -> 3306",
236
+				"exposed by route/frontend on pod port 8080",
237
+				"svc/frontend - 172.30.17.154:5432 -> 8080",
237 238
 				"database deploys",
238 239
 				"frontend deploys",
239 240
 				"with docker.io/openshift/ruby-20-centos7:latest",
... ...
@@ -18,5 +18,4 @@ Kubernetes Service port and let it do the load balancing and routing. Alternatel
18 18
 a more meaningful implementation of a router could take the endpoints for the service
19 19
 and route/load balance the incoming requests to the corresponding service endpoints.
20 20
 */
21
-
22 21
 package route
23 22
new file mode 100644
... ...
@@ -0,0 +1,65 @@
0
+package analysis
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"github.com/gonum/graph"
6
+
7
+	osgraph "github.com/openshift/origin/pkg/api/graph"
8
+	kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
9
+	routeedges "github.com/openshift/origin/pkg/route/graph"
10
+	routegraph "github.com/openshift/origin/pkg/route/graph/nodes"
11
+)
12
+
13
+const (
14
+	// MissingRoutePortWarning is returned when a route has no route port specified
15
+	// and the service it routes to has multiple ports.
16
+	MissingRoutePortWarning = "MissingRoutePort"
17
+	// MissingServiceWarning is returned when there is no service for the specific route.
18
+	MissingServiceWarning = "MissingService"
19
+)
20
+
21
+// FindMissingPortMapping checks all routes and reports those that don't specify a port while
22
+// the service they are routing to, has multiple ports. Also if a service for a route doesn't
23
+// exist, will be reported.
24
+func FindMissingPortMapping(g osgraph.Graph) []osgraph.Marker {
25
+	markers := []osgraph.Marker{}
26
+
27
+route:
28
+	for _, uncastRouteNode := range g.NodesByKind(routegraph.RouteNodeKind) {
29
+		for _, uncastServiceNode := range g.SuccessorNodesByEdgeKind(uncastRouteNode, routeedges.ExposedThroughRouteEdgeKind) {
30
+			routeNode := uncastRouteNode.(*routegraph.RouteNode)
31
+			svcNode := uncastServiceNode.(*kubegraph.ServiceNode)
32
+
33
+			if !svcNode.Found() {
34
+				markers = append(markers, osgraph.Marker{
35
+					Node:         routeNode,
36
+					RelatedNodes: []graph.Node{svcNode},
37
+
38
+					Severity: osgraph.WarningSeverity,
39
+					Key:      MissingServiceWarning,
40
+					Message: fmt.Sprintf("%s is supposed to route traffic to %s but %s doesn't exist.",
41
+						routeNode.ResourceString(), svcNode.ResourceString(), svcNode.ResourceString()),
42
+				})
43
+
44
+				continue route
45
+			}
46
+
47
+			if len(svcNode.Spec.Ports) > 1 && (routeNode.Spec.Port == nil || len(routeNode.Spec.Port.TargetPort.String()) == 0) {
48
+				markers = append(markers, osgraph.Marker{
49
+					Node:         routeNode,
50
+					RelatedNodes: []graph.Node{svcNode},
51
+
52
+					Severity: osgraph.WarningSeverity,
53
+					Key:      MissingRoutePortWarning,
54
+					Message: fmt.Sprintf("%s doesn't have a port specified and is routing traffic to %s which uses multiple ports.",
55
+						routeNode.ResourceString(), svcNode.ResourceString()),
56
+				})
57
+
58
+				continue route
59
+			}
60
+		}
61
+	}
62
+
63
+	return markers
64
+}
0 65
new file mode 100644
... ...
@@ -0,0 +1,40 @@
0
+package analysis
1
+
2
+import (
3
+	"testing"
4
+
5
+	osgraphtest "github.com/openshift/origin/pkg/api/graph/test"
6
+	routeedges "github.com/openshift/origin/pkg/route/graph"
7
+)
8
+
9
+func TestMissingPortMapping(t *testing.T) {
10
+	// Multiple service ports - no route port specified
11
+	g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/missing-route-port.yaml")
12
+	if err != nil {
13
+		t.Fatalf("unexpected error: %v", err)
14
+	}
15
+	routeedges.AddAllRouteEdges(g)
16
+
17
+	markers := FindMissingPortMapping(g)
18
+	if expected, got := 1, len(markers); expected != got {
19
+		t.Fatalf("expected %d markers, got %d", expected, got)
20
+	}
21
+	if expected, got := MissingRoutePortWarning, markers[0].Key; expected != got {
22
+		t.Fatalf("expected %s marker key, got %s", expected, got)
23
+	}
24
+
25
+	// Dangling route
26
+	g, _, err = osgraphtest.BuildGraph("../../../api/graph/test/lonely-route.yaml")
27
+	if err != nil {
28
+		t.Fatalf("unexpected error: %v", err)
29
+	}
30
+	routeedges.AddAllRouteEdges(g)
31
+
32
+	markers = FindMissingPortMapping(g)
33
+	if expected, got := 1, len(markers); expected != got {
34
+		t.Fatalf("expected %d markers, got %d", expected, got)
35
+	}
36
+	if expected, got := MissingServiceWarning, markers[0].Key; expected != got {
37
+		t.Fatalf("expected %s marker key, got %s", expected, got)
38
+	}
39
+}
0 40
new file mode 100644
... ...
@@ -0,0 +1,3 @@
0
+// Package analysis provides functions that analyse routes and setup markers
1
+// that will be reported by oc status
2
+package analysis
0 3
new file mode 100644
... ...
@@ -0,0 +1,2 @@
0
+// Package graph contains graph utilities for routes
1
+package graph
0 2
new file mode 100644
... ...
@@ -0,0 +1,36 @@
0
+package graph
1
+
2
+import (
3
+	"github.com/gonum/graph"
4
+
5
+	kapi "k8s.io/kubernetes/pkg/api"
6
+
7
+	osgraph "github.com/openshift/origin/pkg/api/graph"
8
+	kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
9
+	routegraph "github.com/openshift/origin/pkg/route/graph/nodes"
10
+)
11
+
12
+const (
13
+	// ExposedThroughRouteEdgeKind is an edge from a route to any object that
14
+	// is exposed through routes
15
+	ExposedThroughRouteEdgeKind = "ExposedThroughRoute"
16
+)
17
+
18
+// AddRouteEdges adds an edge that connect a service to a route in the given graph
19
+func AddRouteEdges(g osgraph.MutableUniqueGraph, node *routegraph.RouteNode) {
20
+	syntheticService := &kapi.Service{}
21
+	syntheticService.Namespace = node.Namespace
22
+	syntheticService.Name = node.Spec.To.Name
23
+
24
+	serviceNode := kubegraph.FindOrCreateSyntheticServiceNode(g, syntheticService)
25
+	g.AddEdge(node, serviceNode, ExposedThroughRouteEdgeKind)
26
+}
27
+
28
+// AddAllRouteEdges adds service edges to all route nodes in the given graph
29
+func AddAllRouteEdges(g osgraph.MutableUniqueGraph) {
30
+	for _, node := range g.(graph.Graph).Nodes() {
31
+		if routeNode, ok := node.(*routegraph.RouteNode); ok {
32
+			AddRouteEdges(g, routeNode)
33
+		}
34
+	}
35
+}
0 36
new file mode 100644
... ...
@@ -0,0 +1,2 @@
0
+// Package nodes contains graph functions and types for routes
1
+package nodes
0 2
new file mode 100644
... ...
@@ -0,0 +1,22 @@
0
+package nodes
1
+
2
+import (
3
+	"github.com/gonum/graph"
4
+
5
+	osgraph "github.com/openshift/origin/pkg/api/graph"
6
+	routeapi "github.com/openshift/origin/pkg/route/api"
7
+)
8
+
9
+// EnsureRouteNode adds a graph node for the specific route if it does not exist
10
+func EnsureRouteNode(g osgraph.MutableUniqueGraph, route *routeapi.Route) *RouteNode {
11
+	return osgraph.EnsureUnique(
12
+		g,
13
+		RouteNodeName(route),
14
+		func(node osgraph.Node) graph.Node {
15
+			return &RouteNode{
16
+				Node:  node,
17
+				Route: route,
18
+			}
19
+		},
20
+	).(*RouteNode)
21
+}
0 22
new file mode 100644
... ...
@@ -0,0 +1,37 @@
0
+package nodes
1
+
2
+import (
3
+	"reflect"
4
+
5
+	osgraph "github.com/openshift/origin/pkg/api/graph"
6
+	routeapi "github.com/openshift/origin/pkg/route/api"
7
+)
8
+
9
+var (
10
+	RouteNodeKind = reflect.TypeOf(routeapi.Route{}).Name()
11
+)
12
+
13
+func RouteNodeName(o *routeapi.Route) osgraph.UniqueName {
14
+	return osgraph.GetUniqueRuntimeObjectNodeName(RouteNodeKind, o)
15
+}
16
+
17
+type RouteNode struct {
18
+	osgraph.Node
19
+	*routeapi.Route
20
+}
21
+
22
+func (n RouteNode) Object() interface{} {
23
+	return n.Route
24
+}
25
+
26
+func (n RouteNode) String() string {
27
+	return string(RouteNodeName(n.Route))
28
+}
29
+
30
+func (n RouteNode) ResourceString() string {
31
+	return "route/" + n.Name
32
+}
33
+
34
+func (*RouteNode) Kind() string {
35
+	return RouteNodeKind
36
+}
... ...
@@ -470,6 +470,24 @@ items:
470 470
     type: ClusterIP
471 471
   status:
472 472
     loadBalancer: {}
473
+- apiVersion: v1beta3
474
+  kind: Route
475
+  metadata:
476
+    annotations:
477
+      openshift.io/host.generated: "true"
478
+    creationTimestamp: 2015-04-07T04:12:17Z
479
+    labels:
480
+      template: application-template-stibuild
481
+    name: frontend
482
+    namespace: example
483
+    resourceVersion: "393"
484
+  spec:
485
+    host: frontend-example.router.default.svc.cluster.local
486
+    port:
487
+      targetPort: 8080
488
+    to:
489
+      kind: Service
490
+      name: frontend
473 491
 kind: List
474 492
 metadata:
475 493
   resourceVersion: "592"