This commit adds graph pieces for routes and makes `oc status` report routes with missing route ports.
| ... | ... |
@@ -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 | 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 | 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" |