Skip to content

Commit

Permalink
Implements Gateway API for Controller Runtime
Browse files Browse the repository at this point in the history
Signed-off-by: Daneyon Hansen <daneyonhansen@gmail.com>
  • Loading branch information
danehans committed May 10, 2021
1 parent 0407937 commit 91ab66a
Show file tree
Hide file tree
Showing 23 changed files with 1,768 additions and 94 deletions.
9 changes: 7 additions & 2 deletions cmd/contour/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,10 +433,15 @@ func doServe(log logrus.FieldLogger, ctx *serveContext) error {

err = gatewayapi_v1alpha1.AddToScheme(mgr.GetScheme())
if err != nil {
log.Error(err, "unable to add GatewayAPI to scheme.")
log.Error(err, "unable to add Gateway API to scheme.")
os.Exit(1)
}

// Create and register the GatewayClass controller with the manager.
if _, err := contour_cache.NewGatewayClassController(mgr, &dynamicHandler, log.WithField("context", "gatewayclass-controller")); err != nil {
log.WithError(err).Fatal("failed to create gatewayclass-controller")
}

// Create and register the NewGatewayController controller with the manager.
if _, err := contour_cache.NewGatewayController(mgr, &dynamicHandler, log.WithField("context", "gateway-controller")); err != nil {
log.WithError(err).Fatal("failed to create gateway-controller")
Expand All @@ -462,7 +467,7 @@ func doServe(log logrus.FieldLogger, ctx *serveContext) error {
return mgr.Start(signals.SetupSignalHandler())
})
} else {
log.Fatalf("GatewayAPI Gateway configured but APIs not installed in cluster.")
log.Fatalf("Gateway API configured but CRDs not installed in cluster.")
}
}

Expand Down
5 changes: 5 additions & 0 deletions examples/contour/02-role-contour.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,11 @@ rules:
resources:
- services
verbs:
- create
- delete
- get
- list
- update
- watch
- apiGroups:
- apiextensions.k8s.io
Expand Down Expand Up @@ -83,6 +86,7 @@ rules:
- networking.x-k8s.io
resources:
- backendpolicies
- gatewayclasses
- gateways
- httproutes
- tcproutes
Expand All @@ -96,6 +100,7 @@ rules:
- networking.x-k8s.io
resources:
- backendpolicies/status
- gatewayclasses/status
- httproutes/status
- tcproutes/status
- tlsroutes/status
Expand Down
5 changes: 5 additions & 0 deletions examples/render/contour.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2785,8 +2785,11 @@ rules:
resources:
- services
verbs:
- create
- delete
- get
- list
- update
- watch
- apiGroups:
- apiextensions.k8s.io
Expand Down Expand Up @@ -2822,6 +2825,7 @@ rules:
- networking.x-k8s.io
resources:
- backendpolicies
- gatewayclasses
- gateways
- httproutes
- tcproutes
Expand All @@ -2835,6 +2839,7 @@ rules:
- networking.x-k8s.io
resources:
- backendpolicies/status
- gatewayclasses/status
- httproutes/status
- tcproutes/status
- tlsroutes/status
Expand Down
30 changes: 30 additions & 0 deletions internal/dag/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"github.com/projectcontour/contour/internal/annotation"
"github.com/projectcontour/contour/internal/k8s"
ingress_validation "github.com/projectcontour/contour/internal/validation/ingress"
"github.com/projectcontour/contour/pkg/config"

"github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
networking_v1 "k8s.io/api/networking/v1"
Expand Down Expand Up @@ -60,6 +62,7 @@ type KubernetesCache struct {
tlscertificatedelegations map[types.NamespacedName]*contour_api_v1.TLSCertificateDelegation
services map[types.NamespacedName]*v1.Service
namespaces map[string]*v1.Namespace
gatewayclass *gatewayapi_v1alpha1.GatewayClass
gateway *gatewayapi_v1alpha1.Gateway
httproutes map[types.NamespacedName]*gatewayapi_v1alpha1.HTTPRoute
tlsroutes map[types.NamespacedName]*gatewayapi_v1alpha1.TLSRoute
Expand Down Expand Up @@ -139,6 +142,22 @@ func (kc *KubernetesCache) matchesIngressClassAnnotation(obj metav1.Object) bool
return true
}

// matchesGatewayClassController returns true if the given Kubernetes object
// belongs to the GatewayClass that this cache is using.
func (kc *KubernetesCache) matchesGatewayClassController(obj *gatewayapi_v1alpha1.GatewayClass) bool {
if obj.Spec.Controller != config.ContourGatewayClass {
kind := k8s.KindOf(obj)

kc.WithField("name", obj.GetName()).
WithField("namespace", obj.GetNamespace()).
WithField("kind", kind).
WithField("configured gatewayclass name", obj.Name).
Debug("ignoring object with unmatched gatewayclass controller")
return false
}
return true
}

// matchesGateway returns true if the given Kubernetes object
// belongs to the Gateway that this cache is using.
func (kc *KubernetesCache) matchesGateway(obj *gatewayapi_v1alpha1.Gateway) bool {
Expand Down Expand Up @@ -233,6 +252,11 @@ func (kc *KubernetesCache) Insert(obj interface{}) bool {
case *contour_api_v1.TLSCertificateDelegation:
kc.tlscertificatedelegations[k8s.NamespacedNameOf(obj)] = obj
return true
case *gatewayapi_v1alpha1.GatewayClass:
if kc.matchesGatewayClassController(obj) {
kc.gatewayclass = obj
return true
}
case *gatewayapi_v1alpha1.Gateway:
if kc.matchesGateway(obj) {
kc.gateway = obj
Expand Down Expand Up @@ -426,6 +450,12 @@ func (kc *KubernetesCache) remove(obj interface{}) bool {
_, ok := kc.tlscertificatedelegations[m]
delete(kc.tlscertificatedelegations, m)
return ok
case *gatewayapi_v1alpha1.GatewayClass:
if kc.matchesGatewayClassController(obj) {
kc.gatewayclass = nil
return true
}
return false
case *gatewayapi_v1alpha1.Gateway:
if kc.matchesGateway(obj) {
kc.gateway = nil
Expand Down
142 changes: 69 additions & 73 deletions internal/dag/gatewayapi_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ type matchConditions struct {
headerMatchCondition []HeaderMatchCondition
}

// Run translates Service APIs into DAG objects and
// Run translates Gateway API objects into DAG objects and
// adds them to the DAG.
func (p *GatewayAPIProcessor) Run(dag *DAG, source *KubernetesCache) {
p.dag = dag
Expand All @@ -64,100 +64,96 @@ func (p *GatewayAPIProcessor) Run(dag *DAG, source *KubernetesCache) {
p.source = nil
}()

// Gateway must be defined for resources to be processed.
if p.source.gateway == nil {
p.Error("Gateway is not defined!")
return
}
if p.source.gateway != nil {
for _, listener := range p.source.gateway.Spec.Listeners {

for _, listener := range p.source.gateway.Spec.Listeners {
var matchingRoutes []*gatewayapi_v1alpha1.HTTPRoute
var listenerSecret *Secret

var matchingRoutes []*gatewayapi_v1alpha1.HTTPRoute
var listenerSecret *Secret
// Validate the Kind on the selector is a supported type.
switch listener.Protocol {
case gatewayapi_v1alpha1.HTTPSProtocolType, gatewayapi_v1alpha1.TLSProtocolType:
// Validate that if protocol is type HTTPS or TLS that TLS is defined.
if listener.TLS == nil {
p.Errorf("Listener.TLS is required when protocol is %q.", listener.Protocol)
continue
}

// Validate the Kind on the selector is a supported type.
switch listener.Protocol {
case gatewayapi_v1alpha1.HTTPSProtocolType, gatewayapi_v1alpha1.TLSProtocolType:
// Validate that if protocol is type HTTPS or TLS that TLS is defined.
if listener.TLS == nil {
p.Errorf("Listener.TLS is required when protocol is %q.", listener.Protocol)
// Check for TLS on the Gateway.
if listenerSecret = p.validGatewayTLS(listener); listenerSecret == nil {
// If TLS was configured on the Listener, but it's invalid, don't allow any
// routes to be bound to this listener since it can't serve TLS traffic.
continue
}
case gatewayapi_v1alpha1.HTTPProtocolType:
break
default:
p.Errorf("Listener.Protocol %q is not supported.", listener.Protocol)
continue
}

// Check for TLS on the Gateway.
if listenerSecret = p.validGatewayTLS(listener); listenerSecret == nil {
// If TLS was configured on the Listener, but it's invalid, don't allow any
// routes to be bound to this listener since it can't serve TLS traffic.
continue
// Validate the Group on the selector is a supported type.
if listener.Routes.Group != nil {
if *listener.Routes.Group != gatewayapi_v1alpha1.GroupName {
p.Errorf("Listener.Routes.Group %q is not supported.", listener.Routes.Group)
continue
}
}
case gatewayapi_v1alpha1.HTTPProtocolType:
break
default:
p.Errorf("Listener.Protocol %q is not supported.", listener.Protocol)
continue
}

// Validate the Group on the selector is a supported type.
if listener.Routes.Group != nil {
if *listener.Routes.Group != gatewayapi_v1alpha1.GroupName {
p.Errorf("Listener.Routes.Group %q is not supported.", listener.Routes.Group)
// Validate the Kind on the selector is a supported type.
if listener.Routes.Kind != KindHTTPRoute {
p.Errorf("Listener.Routes.Kind %q is not supported.", listener.Routes.Kind)
continue
}
}

// Validate the Kind on the selector is a supported type.
if listener.Routes.Kind != KindHTTPRoute {
p.Errorf("Listener.Routes.Kind %q is not supported.", listener.Routes.Kind)
continue
}
for _, route := range p.source.httproutes {

for _, route := range p.source.httproutes {
// Filter the HTTPRoutes that match the gateway which Contour is configured to watch.
// RouteBindingSelector defines a schema for associating routes with the Gateway.
// If Namespaces and Selector are defined, only routes matching both selectors are associated with the Gateway.

// Filter the HTTPRoutes that match the gateway which Contour is configured to watch.
// RouteBindingSelector defines a schema for associating routes with the Gateway.
// If Namespaces and Selector are defined, only routes matching both selectors are associated with the Gateway.
// ## RouteBindingSelector ##
//
// Selector specifies a set of route labels used for selecting routes to associate
// with the Gateway. If this Selector is defined, only routes matching the Selector
// are associated with the Gateway. An empty Selector matches all routes.

// ## RouteBindingSelector ##
//
// Selector specifies a set of route labels used for selecting routes to associate
// with the Gateway. If this Selector is defined, only routes matching the Selector
// are associated with the Gateway. An empty Selector matches all routes.

nsMatches, err := p.namespaceMatches(listener.Routes.Namespaces, route)
if err != nil {
p.Errorf("error validating namespaces against Listener.Routes.Namespaces: %s", err)
}
nsMatches, err := p.namespaceMatches(listener.Routes.Namespaces, route)
if err != nil {
p.Errorf("error validating namespaces against Listener.Routes.Namespaces: %s", err)
}

selMatches, err := selectorMatches(listener.Routes.Selector, route.Labels)
if err != nil {
p.Errorf("error validating routes against Listener.Routes.Selector: %s", err)
}
selMatches, err := selectorMatches(listener.Routes.Selector, route.Labels)
if err != nil {
p.Errorf("error validating routes against Listener.Routes.Selector: %s", err)
}

// If all the match criteria for this HTTPRoute match the Gateway, then add
// the route to the set of matchingRoutes.
if selMatches && nsMatches {
// If all the match criteria for this HTTPRoute match the Gateway, then add
// the route to the set of matchingRoutes.
if selMatches && nsMatches {

gatewayAllowMatches := p.gatewayMatches(route)
if (listener.Routes.Selector != nil || listener.Routes.Namespaces != nil) && !gatewayAllowMatches {
gatewayAllowMatches := p.gatewayMatches(route)
if (listener.Routes.Selector != nil || listener.Routes.Namespaces != nil) && !gatewayAllowMatches {

// If a label selector or namespace selector matches, but the gateway Allow doesn't
// then set the "Admitted: false" for the route.
routeAccessor, commit := p.dag.StatusCache.ConditionsAccessor(k8s.NamespacedNameOf(route), route.Generation, status.ResourceHTTPRoute, route.Status.Gateways)
routeAccessor.AddCondition(gatewayapi_v1alpha1.ConditionRouteAdmitted, metav1.ConditionFalse, status.ReasonGatewayAllowMismatch, "Gateway RouteSelector matches, but GatewayAllow has mismatch.")
commit()
continue
}
// If a label selector or namespace selector matches, but the gateway Allow doesn't
// then set the "Admitted: false" for the route.
routeAccessor, commit := p.dag.StatusCache.ConditionsAccessor(k8s.NamespacedNameOf(route), route.Generation, status.ResourceHTTPRoute, route.Status.Gateways)
routeAccessor.AddCondition(gatewayapi_v1alpha1.ConditionRouteAdmitted, metav1.ConditionFalse, status.ReasonGatewayAllowMismatch, "Gateway RouteSelector matches, but GatewayAllow has mismatch.")
commit()
continue
}

if gatewayAllowMatches {
// Empty Selector matches all routes.
matchingRoutes = append(matchingRoutes, route)
if gatewayAllowMatches {
// Empty Selector matches all routes.
matchingRoutes = append(matchingRoutes, route)
}
}
}
}

// Process all the routes that match this Gateway.
for _, matchingRoute := range matchingRoutes {
p.computeHTTPRoute(matchingRoute, listenerSecret)
// Process all the routes that match this Gateway.
for _, matchingRoute := range matchingRoutes {
p.computeHTTPRoute(matchingRoute, listenerSecret)
}
}
}
}
Expand Down
Loading

0 comments on commit 91ab66a

Please sign in to comment.