package analysis import ( "fmt" "strconv" "github.com/gonum/graph" kapi "k8s.io/kubernetes/pkg/api" osgraph "github.com/openshift/origin/pkg/api/graph" kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes" routeapi "github.com/openshift/origin/pkg/route/api" routeedges "github.com/openshift/origin/pkg/route/graph" routegraph "github.com/openshift/origin/pkg/route/graph/nodes" ) const ( // MissingRoutePortWarning is returned when a route has no route port specified // and the service it routes to has multiple ports. MissingRoutePortWarning = "MissingRoutePort" // WrongRoutePortWarning is returned when a route has a route port specified // but the service it points to has no such port (either as a named port or as // a target port). WrongRoutePortWarning = "WrongRoutePort" // MissingServiceWarning is returned when there is no service for the specific route. MissingServiceWarning = "MissingService" // MissingTLSTerminationTypeErr is returned when a route with a tls config doesn't // specify a tls termination type. MissingTLSTerminationTypeErr = "MissingTLSTermination" // PathBasedPassthroughErr is returned when a path based route is passthrough // terminated. PathBasedPassthroughErr = "PathBasedPassthrough" // MissingTLSTerminationTypeErr is returned when a route with a tls config doesn't // specify a tls termination type. RouteNotAdmittedTypeErr = "RouteNotAdmitted" // MissingRequiredRouterErr is returned when no router has been setup. MissingRequiredRouterErr = "MissingRequiredRouter" ) // FindPortMappingIssues checks all routes and reports any issues related to their ports. // Also non-existent services for routes are reported here. func FindPortMappingIssues(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker { markers := []osgraph.Marker{} for _, uncastRouteNode := range g.NodesByKind(routegraph.RouteNodeKind) { routeNode := uncastRouteNode.(*routegraph.RouteNode) marker := routePortMarker(g, f, routeNode) if marker != nil { markers = append(markers, *marker) } } return markers } func routePortMarker(g osgraph.Graph, f osgraph.Namer, routeNode *routegraph.RouteNode) *osgraph.Marker { for _, uncastServiceNode := range g.SuccessorNodesByEdgeKind(routeNode, routeedges.ExposedThroughRouteEdgeKind) { svcNode := uncastServiceNode.(*kubegraph.ServiceNode) if !svcNode.Found() { return &osgraph.Marker{ Node: routeNode, RelatedNodes: []graph.Node{svcNode}, Severity: osgraph.WarningSeverity, Key: MissingServiceWarning, Message: fmt.Sprintf("%s is supposed to route traffic to %s but %s doesn't exist.", f.ResourceName(routeNode), f.ResourceName(svcNode), f.ResourceName(svcNode)), // TODO: Suggest 'oc create service' once that's a thing. // See https://github.com/kubernetes/kubernetes/pull/19509 } } if len(svcNode.Spec.Ports) > 1 && (routeNode.Spec.Port == nil || len(routeNode.Spec.Port.TargetPort.String()) == 0) { return &osgraph.Marker{ Node: routeNode, RelatedNodes: []graph.Node{svcNode}, Severity: osgraph.WarningSeverity, Key: MissingRoutePortWarning, Message: fmt.Sprintf("%s doesn't have a port specified and is routing traffic to %s which uses multiple ports.", f.ResourceName(routeNode), f.ResourceName(svcNode)), } } if routeNode.Spec.Port == nil { // If no port is specified, we don't need to analyze any further. return nil } routePortString := routeNode.Spec.Port.TargetPort.String() if routePort, err := strconv.Atoi(routePortString); err == nil { for _, port := range svcNode.Spec.Ports { if port.TargetPort.IntValue() == routePort { return nil } } // route has a numeric port, service has no port with that number as a targetPort. marker := &osgraph.Marker{ Node: routeNode, RelatedNodes: []graph.Node{svcNode}, Severity: osgraph.WarningSeverity, Key: WrongRoutePortWarning, Message: fmt.Sprintf("%s has a port specified (%d) but %s has no such targetPort.", f.ResourceName(routeNode), routePort, f.ResourceName(svcNode)), } if len(svcNode.Spec.Ports) == 1 { marker.Suggestion = osgraph.Suggestion(fmt.Sprintf("oc patch %s -p '{\"spec\":{\"port\":{\"targetPort\": %d}}}'", f.ResourceName(routeNode), svcNode.Spec.Ports[0].TargetPort.IntValue())) } return marker } for _, port := range svcNode.Spec.Ports { if port.Name == routePortString { return nil } } // route has a named port, service has no port with that name. marker := &osgraph.Marker{ Node: routeNode, RelatedNodes: []graph.Node{svcNode}, Severity: osgraph.WarningSeverity, Key: WrongRoutePortWarning, Message: fmt.Sprintf("%s has a named port specified (%q) but %s has no such named port.", f.ResourceName(routeNode), routePortString, f.ResourceName(svcNode)), } if len(svcNode.Spec.Ports) == 1 { marker.Suggestion = osgraph.Suggestion(fmt.Sprintf("oc patch %s -p '{\"spec\":{\"port\":{\"targetPort\": %d}}}'", f.ResourceName(routeNode), svcNode.Spec.Ports[0].TargetPort.IntValue())) } return marker } return nil } func FindMissingTLSTerminationType(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker { markers := []osgraph.Marker{} for _, uncastRouteNode := range g.NodesByKind(routegraph.RouteNodeKind) { routeNode := uncastRouteNode.(*routegraph.RouteNode) if routeNode.Spec.TLS != nil && len(routeNode.Spec.TLS.Termination) == 0 { markers = append(markers, osgraph.Marker{ Node: routeNode, Severity: osgraph.ErrorSeverity, Key: MissingTLSTerminationTypeErr, Message: fmt.Sprintf("%s has a TLS configuration but no termination type specified.", f.ResourceName(routeNode)), Suggestion: osgraph.Suggestion(fmt.Sprintf("oc patch %s -p '{\"spec\":{\"tls\":{\"termination\":\"\"}}}' (replace with a valid termination type: edge, passthrough, reencrypt)", f.ResourceName(routeNode)))}) } } return markers } // FindRouteAdmissionFailures creates markers for any routes that were rejected by their routers func FindRouteAdmissionFailures(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker { markers := []osgraph.Marker{} for _, uncastRouteNode := range g.NodesByKind(routegraph.RouteNodeKind) { routeNode := uncastRouteNode.(*routegraph.RouteNode) Route: for _, ingress := range routeNode.Status.Ingress { switch status, condition := routeapi.IngressConditionStatus(&ingress, routeapi.RouteAdmitted); status { case kapi.ConditionFalse: markers = append(markers, osgraph.Marker{ Node: routeNode, Severity: osgraph.ErrorSeverity, Key: RouteNotAdmittedTypeErr, Message: fmt.Sprintf("%s was not accepted by router %q: %s (%s)", f.ResourceName(routeNode), ingress.RouterName, condition.Message, condition.Reason), }) break Route } } } return markers } // FindMissingRouter creates markers for all routes in case there is no running router. func FindMissingRouter(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker { markers := []osgraph.Marker{} for _, uncastRouteNode := range g.NodesByKind(routegraph.RouteNodeKind) { routeNode := uncastRouteNode.(*routegraph.RouteNode) if len(routeNode.Route.Status.Ingress) == 0 { markers = append(markers, osgraph.Marker{ Node: routeNode, Severity: osgraph.ErrorSeverity, Key: MissingRequiredRouterErr, Message: fmt.Sprintf("%s is routing traffic to svc/%s, but either the administrator has not installed a router or the router is not selecting this route.", f.ResourceName(routeNode), routeNode.Spec.To.Name), Suggestion: osgraph.Suggestion("oc adm router -h"), }) } } return markers } func FindPathBasedPassthroughRoutes(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker { markers := []osgraph.Marker{} for _, uncastRouteNode := range g.NodesByKind(routegraph.RouteNodeKind) { routeNode := uncastRouteNode.(*routegraph.RouteNode) if len(routeNode.Spec.Path) > 0 && routeNode.Spec.TLS != nil && routeNode.Spec.TLS.Termination == routeapi.TLSTerminationPassthrough { markers = append(markers, osgraph.Marker{ Node: routeNode, Severity: osgraph.ErrorSeverity, Key: PathBasedPassthroughErr, Message: fmt.Sprintf("%s is path-based and uses passthrough termination, which is an invalid combination.", f.ResourceName(routeNode)), Suggestion: osgraph.Suggestion(fmt.Sprintf("1. use spec.tls.termination=edge or 2. use spec.tls.termination=reencrypt and specify spec.tls.destinationCACertificate or 3. remove spec.path")), }) } } return markers }