Skip to content

Commit 7d05284

Browse files
committed
reference grant
1 parent 36a1b7a commit 7d05284

File tree

8 files changed

+735
-47
lines changed

8 files changed

+735
-47
lines changed

.github/workflows/gateway.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,11 @@ jobs:
116116
--organization=sigs.k8s.io \
117117
--project=cloud-provider-kind \
118118
--url=https://github.com/kubernetes-sigs/cloud-provider-kind \
119-
--version=test \
120-
--contact=antonio.ojea.garcia@gmail.com \
119+
--version=${{ github.sha }} \
120+
--contact=https://github.com/kubernetes-sigs/cloud-provider-kind/issues/new \
121121
--gateway-class=cloud-provider-kind \
122-
--supported-features=Gateway,HTTPRoute \
122+
--conformance-profiles=GATEWAY-HTTP \
123+
--supported-features=Gateway,HTTPRoute,ReferenceGrant \
123124
--report-output=./_artifacts/conformance.yaml
124125
125126
- name: Export logs

pkg/controller/controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ func startCloudControllerManager(ctx context.Context, clusterName string, config
304304
sharedGwInformers.Gateway().V1().Gateways(),
305305
sharedGwInformers.Gateway().V1().HTTPRoutes(),
306306
sharedGwInformers.Gateway().V1().GRPCRoutes(),
307+
sharedGwInformers.Gateway().V1beta1().ReferenceGrants(),
307308
)
308309
if err != nil {
309310
klog.Errorf("Failed to start gateway controller: %v", err)

pkg/gateway/controller.go

Lines changed: 174 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
apierrors "k8s.io/apimachinery/pkg/api/errors"
4343
"k8s.io/apimachinery/pkg/api/meta"
4444
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
45+
"k8s.io/apimachinery/pkg/labels"
4546
"k8s.io/apimachinery/pkg/util/runtime"
4647
"k8s.io/apimachinery/pkg/util/wait"
4748
corev1informers "k8s.io/client-go/informers/core/v1"
@@ -55,9 +56,12 @@ import (
5556
"sigs.k8s.io/cloud-provider-kind/pkg/config"
5657
"sigs.k8s.io/cloud-provider-kind/pkg/tunnels"
5758
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
59+
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
5860
gatewayclient "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned"
5961
gatewayinformers "sigs.k8s.io/gateway-api/pkg/client/informers/externalversions/apis/v1"
62+
gatewayinformersv1beta1 "sigs.k8s.io/gateway-api/pkg/client/informers/externalversions/apis/v1beta1"
6063
gatewaylisters "sigs.k8s.io/gateway-api/pkg/client/listers/apis/v1"
64+
gatewaylistersv1beta1 "sigs.k8s.io/gateway-api/pkg/client/listers/apis/v1beta1"
6165
)
6266

6367
const (
@@ -113,6 +117,9 @@ type Controller struct {
113117
grpcrouteLister gatewaylisters.GRPCRouteLister
114118
grpcrouteListerSynced cache.InformerSynced
115119

120+
referenceGrantLister gatewaylistersv1beta1.ReferenceGrantLister
121+
referenceGrantListerSynced cache.InformerSynced
122+
116123
xdscache cachev3.SnapshotCache
117124
xdsserver serverv3.Server
118125
xdsLocalAddress string
@@ -133,6 +140,7 @@ func New(
133140
gatewayInformer gatewayinformers.GatewayInformer,
134141
httprouteInformer gatewayinformers.HTTPRouteInformer,
135142
grpcrouteInformer gatewayinformers.GRPCRouteInformer,
143+
referenceGrantInformer gatewayinformersv1beta1.ReferenceGrantInformer,
136144
) (*Controller, error) {
137145
c := &Controller{
138146
clusterName: clusterName,
@@ -152,10 +160,12 @@ func New(
152160
workqueue.DefaultTypedControllerRateLimiter[string](),
153161
workqueue.TypedRateLimitingQueueConfig[string]{Name: "gateway"},
154162
),
155-
httprouteLister: httprouteInformer.Lister(),
156-
httprouteListerSynced: httprouteInformer.Informer().HasSynced,
157-
grpcrouteLister: grpcrouteInformer.Lister(),
158-
grpcrouteListerSynced: grpcrouteInformer.Informer().HasSynced,
163+
httprouteLister: httprouteInformer.Lister(),
164+
httprouteListerSynced: httprouteInformer.Informer().HasSynced,
165+
grpcrouteLister: grpcrouteInformer.Lister(),
166+
grpcrouteListerSynced: grpcrouteInformer.Informer().HasSynced,
167+
referenceGrantLister: referenceGrantInformer.Lister(),
168+
referenceGrantListerSynced: referenceGrantInformer.Informer().HasSynced,
159169
}
160170
_, err := gatewayClassInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
161171
AddFunc: func(obj interface{}) {
@@ -279,6 +289,15 @@ func New(
279289
return nil, err
280290
}
281291

292+
_, err = referenceGrantInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
293+
AddFunc: c.processReferenceGrant,
294+
UpdateFunc: func(old, new interface{}) { c.processReferenceGrant(new) },
295+
DeleteFunc: c.processReferenceGrant,
296+
})
297+
if err != nil {
298+
return nil, err
299+
}
300+
282301
if config.DefaultConfig.LoadBalancerConnectivity == config.Tunnel {
283302
c.tunnelManager = tunnels.NewTunnelManager()
284303
}
@@ -430,6 +449,7 @@ func (c *Controller) Run(ctx context.Context) error {
430449
c.namespaceListerSynced,
431450
c.serviceListerSynced,
432451
c.secretListerSynced,
452+
c.referenceGrantListerSynced,
433453
) {
434454
return fmt.Errorf("timed out waiting for caches to sync")
435455
}
@@ -463,6 +483,156 @@ func (c *Controller) processGateways(references []gatewayv1.ParentReference, loc
463483
}
464484
}
465485

486+
// processReferenceGrant finds all Gateways that may be affected by a change to a
487+
// ReferenceGrant and enqueues them for reconciliation. This function handles grants
488+
// for both cross-namespace BackendRefs (from Routes) and cross-namespace
489+
// SecretRefs (from Gateways).
490+
func (c *Controller) processReferenceGrant(obj interface{}) {
491+
grant, ok := obj.(*gatewayv1beta1.ReferenceGrant)
492+
if !ok {
493+
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
494+
if !ok {
495+
klog.Errorf("error decoding object, invalid type")
496+
return
497+
}
498+
grant, ok = tombstone.Obj.(*gatewayv1beta1.ReferenceGrant)
499+
if !ok {
500+
klog.Errorf("error decoding object tombstone, invalid type")
501+
return
502+
}
503+
}
504+
505+
gatewaysToEnqueue := make(map[string]struct{})
506+
// The ReferenceGrant lives in the namespace of the resource being referenced (the "To" side).
507+
targetNamespace := grant.Namespace
508+
509+
// Check for Grants allowing Routes to reference Services
510+
for _, from := range grant.Spec.From {
511+
// As the controller supports more route types, they should be added here.
512+
if !CloudProviderSupportedKinds.Has(from.Kind) {
513+
continue
514+
}
515+
516+
// Check if the grant allows references TO a Service.
517+
isServiceGrant := false
518+
for _, to := range grant.Spec.To {
519+
if to.Kind == "Service" {
520+
isServiceGrant = true
521+
break
522+
}
523+
}
524+
if !isServiceGrant {
525+
continue
526+
}
527+
528+
// Find all routes in the "From" namespace that could be affected.
529+
httpRoutes, err := c.httprouteLister.HTTPRoutes(string(from.Namespace)).List(labels.Everything())
530+
if err != nil {
531+
klog.Errorf("Failed to list HTTPRoutes in namespace %s: %v", from.Namespace, err)
532+
continue
533+
}
534+
535+
for _, route := range httpRoutes {
536+
if routeReferencesBackendInNamespace(route, targetNamespace) {
537+
// This route is affected. Find its parent Gateways and add them to the queue.
538+
for _, parentRef := range route.Spec.ParentRefs {
539+
if (parentRef.Group != nil && string(*parentRef.Group) != gatewayv1.GroupName) ||
540+
(parentRef.Kind != nil && string(*parentRef.Kind) != "Gateway") {
541+
continue
542+
}
543+
gwNamespace := route.Namespace
544+
if parentRef.Namespace != nil {
545+
gwNamespace = string(*parentRef.Namespace)
546+
}
547+
key := gwNamespace + "/" + string(parentRef.Name)
548+
gatewaysToEnqueue[key] = struct{}{}
549+
}
550+
}
551+
}
552+
}
553+
554+
// Check for Grants allowing Gateways to reference Secrets
555+
for _, from := range grant.Spec.From {
556+
// We are looking for grants FROM Gateways.
557+
if from.Group != gatewayv1.GroupName || from.Kind != "Gateway" {
558+
continue
559+
}
560+
561+
// Check if the grant allows references TO a Secret.
562+
isSecretGrant := false
563+
for _, to := range grant.Spec.To {
564+
if to.Kind == "Secret" {
565+
isSecretGrant = true
566+
break
567+
}
568+
}
569+
if !isSecretGrant {
570+
continue
571+
}
572+
573+
// Find all Gateways in the "From" namespace that could be affected.
574+
gateways, err := c.gatewayLister.Gateways(string(from.Namespace)).List(labels.Everything())
575+
if err != nil {
576+
klog.Errorf("Failed to list Gateways in namespace %s: %v", from.Namespace, err)
577+
continue
578+
}
579+
580+
for _, gw := range gateways {
581+
if gatewayReferencesSecretInNamespace(gw, targetNamespace) {
582+
// This Gateway is affected. Add it to the queue.
583+
key := gw.Namespace + "/" + gw.Name
584+
gatewaysToEnqueue[key] = struct{}{}
585+
}
586+
}
587+
}
588+
589+
// Enqueue all unique Gateways that were found to be affected.
590+
for key := range gatewaysToEnqueue {
591+
c.gatewayqueue.Add(key)
592+
}
593+
}
594+
595+
// routeReferencesBackendInNamespace is a helper to check if an HTTPRoute has a backendRef
596+
// pointing to the specified namespace.
597+
func routeReferencesBackendInNamespace(route *gatewayv1.HTTPRoute, namespace string) bool {
598+
for _, rule := range route.Spec.Rules {
599+
for _, backendRef := range rule.BackendRefs {
600+
backendNamespace := route.Namespace
601+
if backendRef.Namespace != nil {
602+
backendNamespace = string(*backendRef.Namespace)
603+
}
604+
if backendNamespace == namespace {
605+
return true
606+
}
607+
}
608+
}
609+
return false
610+
}
611+
612+
// gatewayReferencesSecretInNamespace is a helper to check if a Gateway has a certificateRef
613+
// pointing to a Secret in the specified namespace.
614+
func gatewayReferencesSecretInNamespace(gateway *gatewayv1.Gateway, namespace string) bool {
615+
for _, listener := range gateway.Spec.Listeners {
616+
if listener.TLS == nil {
617+
continue
618+
}
619+
for _, certRef := range listener.TLS.CertificateRefs {
620+
// We only care about references to Secrets.
621+
if (certRef.Group != nil && *certRef.Group != "") || (certRef.Kind != nil && *certRef.Kind != "Secret") {
622+
continue
623+
}
624+
secretNamespace := gateway.Namespace
625+
if certRef.Namespace != nil {
626+
secretNamespace = string(*certRef.Namespace)
627+
}
628+
if secretNamespace == namespace {
629+
return true
630+
}
631+
}
632+
}
633+
return false
634+
}
635+
466636
func (c *Controller) runGatewayWorker(ctx context.Context) {
467637
for c.processNextGatewayItem(ctx) {
468638
}

pkg/gateway/gateway.go

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ func (c *Controller) buildEnvoyResourcesForGateway(gateway *gatewayv1.Gateway) (
168168
}
169169

170170
// validate listeners that may reuse the same port
171-
conflictedListenerConditions := c.validateListeners(gateway)
171+
listenerValidationConditions := c.validateListeners(gateway)
172172

173173
finalEnvoyListeners := []envoyproxytypes.Resource{}
174174
// Process Listeners by Port
@@ -185,7 +185,7 @@ func (c *Controller) buildEnvoyResourcesForGateway(gateway *gatewayv1.Gateway) (
185185
listenerStatus := gatewayv1.ListenerStatus{
186186
Name: gatewayv1.SectionName(listener.Name),
187187
SupportedKinds: []gatewayv1.RouteGroupKind{},
188-
Conditions: []metav1.Condition{},
188+
Conditions: listenerValidationConditions[listener.Name],
189189
AttachedRoutes: 0,
190190
}
191191
supportedKinds, allKindsValid := getSupportedKinds(listener)
@@ -203,19 +203,30 @@ func (c *Controller) buildEnvoyResourcesForGateway(gateway *gatewayv1.Gateway) (
203203
continue // Stop processing this invalid listener
204204
}
205205

206-
if conflictCondition, isConflicted := conflictedListenerConditions[listener.Name]; isConflicted {
207-
// This listener is conflicted. Set its status and skip it.
208-
meta.SetStatusCondition(&listenerStatus.Conditions, conflictCondition)
206+
isConflicted := meta.IsStatusConditionTrue(listenerStatus.Conditions, string(gatewayv1.ListenerConditionConflicted))
207+
// If the listener is conflicted set its status and skip Envoy config generation.
208+
if isConflicted {
209209
allListenerStatuses[listener.Name] = listenerStatus
210-
continue // DO NOT generate Envoy config for this listener
210+
continue
211+
}
212+
213+
// If there are not references issues then set it to tru
214+
if !meta.IsStatusConditionFalse(listenerStatus.Conditions, string(gatewayv1.ListenerConditionResolvedRefs)) {
215+
meta.SetStatusCondition(&listenerStatus.Conditions, metav1.Condition{
216+
Type: string(gatewayv1.ListenerConditionResolvedRefs),
217+
Status: metav1.ConditionTrue,
218+
Reason: string(gatewayv1.ListenerReasonResolvedRefs),
219+
Message: "All references resolved",
220+
ObservedGeneration: gateway.Generation,
221+
})
211222
}
212223

213224
switch listener.Protocol {
214225
case gatewayv1.HTTPProtocolType, gatewayv1.HTTPSProtocolType:
215226
// Process HTTPRoutes
216227
// Get the routes that were pre-validated for this specific listener.
217228
for _, httpRoute := range routesByListener[listener.Name] {
218-
routes, validBackendRefs, resolvedRefsCondition := translateHTTPRouteToEnvoyRoutes(httpRoute, c.serviceLister)
229+
routes, validBackendRefs, resolvedRefsCondition := translateHTTPRouteToEnvoyRoutes(httpRoute, c.serviceLister, c.referenceGrantLister)
219230

220231
key := types.NamespacedName{Name: httpRoute.Name, Namespace: httpRoute.Namespace}
221232
currentParentStatuses := httpRouteStatuses[key]
@@ -276,15 +287,6 @@ func (c *Controller) buildEnvoyResourcesForGateway(gateway *gatewayv1.Gateway) (
276287

277288
filterChain, err := c.translateListenerToFilterChain(gateway, listener, vhSlice, routeName)
278289
if err != nil {
279-
// If translation fails, a reference is unresolved. Set both conditions to False.
280-
klog.Errorf("Error translating listener %s to filter chain: %v", listener.Name, err)
281-
meta.SetStatusCondition(&listenerStatus.Conditions, metav1.Condition{
282-
Type: string(gatewayv1.ListenerConditionResolvedRefs),
283-
Status: metav1.ConditionFalse,
284-
Reason: string(gatewayv1.ListenerReasonInvalidCertificateRef),
285-
Message: fmt.Sprintf("Failed to resolve references: %v", err),
286-
ObservedGeneration: gateway.Generation,
287-
})
288290
meta.SetStatusCondition(&listenerStatus.Conditions, metav1.Condition{
289291
Type: string(gatewayv1.ListenerConditionProgrammed),
290292
Status: metav1.ConditionFalse,
@@ -293,14 +295,6 @@ func (c *Controller) buildEnvoyResourcesForGateway(gateway *gatewayv1.Gateway) (
293295
ObservedGeneration: gateway.Generation,
294296
})
295297
} else {
296-
// Only if ALL checks pass, set the conditions to True.
297-
meta.SetStatusCondition(&listenerStatus.Conditions, metav1.Condition{
298-
Type: string(gatewayv1.ListenerConditionResolvedRefs),
299-
Status: metav1.ConditionTrue,
300-
Reason: string(gatewayv1.ListenerReasonResolvedRefs),
301-
Message: "All references resolved",
302-
ObservedGeneration: gateway.Generation,
303-
})
304298
meta.SetStatusCondition(&listenerStatus.Conditions, metav1.Condition{
305299
Type: string(gatewayv1.ListenerConditionProgrammed),
306300
Status: metav1.ConditionTrue,

0 commit comments

Comments
 (0)