Skip to content

Commit 892d2d7

Browse files
committed
project/gateway2: Adds GatewayClass SupportedVersion Condition
Previously, the gateway2 controller would unconditionally set the GatewayClass Accepted condition to `True`. This PR updates the CRD watcher to trigger GatewayClass reconciliation. The `ReconcileGatewayClasses()` method was updated to set GatewayClass status conditionas based on the Gateway API spec. Signed-off-by: Daneyon Hansen <[email protected]>
1 parent 733a90f commit 892d2d7

File tree

13 files changed

+462
-39
lines changed

13 files changed

+462
-39
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
changelog:
2+
- type: NEW_FEATURE
3+
issueLink: https://github.com/solo-io/gloo/issues/10092
4+
resolvesIssue: true
5+
description: >-
6+
Adds support for the GatewayClass `SupportedVersion` status condition and updates the ReconcileGatewayClasses()
7+
method to manage status conditions according to the Gateway API specification. The gateway controller will now
8+
reconcile supported Gateway API CRDs and set the `SupportedVersion` status condition based on the value of the
9+
`gateway.networking.k8s.io/bundle-version` annotation.

install/helm/gloo/templates/44-rbac.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ rules:
3030
resources:
3131
- endpointslices
3232
verbs: ["get", "list", "watch"]
33+
- apiGroups:
34+
- "apiextensions.k8s.io"
35+
resources:
36+
- customresourcedefinitions
37+
verbs: ["get", "list", "watch"]
3338
- apiGroups:
3439
- "gateway.solo.io"
3540
resources:

projects/controller/pkg/api/v1/kube/wellknown/types.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ package wellknown
33
import (
44
appsv1 "k8s.io/api/apps/v1"
55
corev1 "k8s.io/api/core/v1"
6+
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
67
)
78

89
var (
910
SecretGVK = corev1.SchemeGroupVersion.WithKind("Secret")
1011
ConfigMapGVK = corev1.SchemeGroupVersion.WithKind("ConfigMap")
1112
ServiceGVK = corev1.SchemeGroupVersion.WithKind("Service")
1213
ServiceAccountGVK = corev1.SchemeGroupVersion.WithKind("ServiceAccount")
13-
14-
DeploymentGVK = appsv1.SchemeGroupVersion.WithKind("Deployment")
14+
CrdGVK = apiextv1.SchemeGroupVersion.WithKind("CustomResourceDefinition")
15+
DeploymentGVK = appsv1.SchemeGroupVersion.WithKind("Deployment")
1516
)

projects/controller/pkg/servers/iosnapshot/history_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
wellknownkube "github.com/solo-io/gloo/projects/controller/pkg/api/v1/kube/wellknown"
1414

1515
corev1 "k8s.io/api/core/v1"
16+
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1617

1718
skmatchers "github.com/solo-io/solo-kit/test/matchers"
1819

@@ -186,6 +187,16 @@ var _ = Describe("History", func() {
186187
"key": "value",
187188
},
188189
},
190+
&apiextv1.CustomResourceDefinition{
191+
ObjectMeta: metav1.ObjectMeta{
192+
Name: "kube-crd",
193+
Namespace: "crd",
194+
ManagedFields: []metav1.ManagedFieldsEntry{{
195+
Manager: "manager",
196+
}},
197+
},
198+
Spec: apiextv1.CustomResourceDefinitionSpec{},
199+
},
189200
&apiv1.Gateway{
190201
ObjectMeta: metav1.ObjectMeta{
191202
Name: "kube-gw",
@@ -420,6 +431,19 @@ var _ = Describe("History", func() {
420431
), fmt.Sprintf("results should contain %v %s.%s", wellknownkube.ConfigMapGVK, "configmap", "kube-configmap"))
421432
})
422433

434+
It("Includes CustomResourceDefinitions", func() {
435+
returnedResources := getInputSnapshotObjects(ctx, history)
436+
Expect(returnedResources).To(ContainElement(
437+
matchers.MatchClientObject(
438+
wellknownkube.CrdGVK,
439+
types.NamespacedName{
440+
Name: "kube-crd",
441+
Namespace: "crd",
442+
},
443+
),
444+
), fmt.Sprintf("results should contain %v %s.%s", wellknownkube.CrdGVK, "crd", "kube-crd"))
445+
})
446+
423447
})
424448

425449
Context("Kubernetes Gateway API Resources", func() {

projects/controller/pkg/servers/iosnapshot/resources.go

+5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ var (
1717
wellknownkube.ConfigMapGVK,
1818
}
1919

20+
KubernetesExtGVKs = []schema.GroupVersionKind{
21+
wellknownkube.CrdGVK,
22+
}
23+
2024
GlooGVKs = []schema.GroupVersionKind{
2125
gloov1.SettingsGVK,
2226
gloov1.UpstreamGVK,
@@ -68,5 +72,6 @@ var (
6872
EdgeOnlyInputSnapshotGVKs,
6973
KubernetesGatewayGVKs,
7074
KubernetesGatewayIntegrationPolicyGVKs,
75+
KubernetesExtGVKs,
7176
)
7277
)

projects/gateway2/controller/controller.go

+116-37
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"strings"
78

89
corev1 "k8s.io/api/core/v1"
910
discoveryv1 "k8s.io/api/discovery/v1"
10-
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
11+
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
12+
kerrors "k8s.io/apimachinery/pkg/api/errors"
1113
"k8s.io/apimachinery/pkg/api/meta"
1214
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1315
"k8s.io/apimachinery/pkg/fields"
@@ -25,10 +27,12 @@ import (
2527
apiv1 "sigs.k8s.io/gateway-api/apis/v1"
2628
apiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
2729
apiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
30+
"sigs.k8s.io/gateway-api/pkg/consts"
2831

2932
gloov1 "github.com/solo-io/gloo/projects/controller/pkg/api/v1/kube/apis/gloo.solo.io/v1"
3033
sologatewayv1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1/kube/apis/gateway.solo.io/v1"
3134
"github.com/solo-io/gloo/projects/gateway2/api/v1alpha1"
35+
"github.com/solo-io/gloo/projects/gateway2/crds"
3236
"github.com/solo-io/gloo/projects/gateway2/deployer"
3337
"github.com/solo-io/gloo/projects/gateway2/extensions"
3438
"github.com/solo-io/gloo/projects/gateway2/query"
@@ -68,14 +72,15 @@ func NewBaseGatewayController(ctx context.Context, cfg GatewayConfig) error {
6872
controllerBuilder := &controllerBuilder{
6973
cfg: cfg,
7074
reconciler: &controllerReconciler{
71-
cli: cfg.Mgr.GetClient(),
72-
scheme: cfg.Mgr.GetScheme(),
73-
kick: cfg.Kick,
75+
controllerName: cfg.ControllerName,
76+
cli: cfg.Mgr.GetClient(),
77+
scheme: cfg.Mgr.GetScheme(),
78+
kick: cfg.Kick,
7479
},
7580
}
7681

7782
return run(ctx,
78-
controllerBuilder.watchCustomResourceDefinitions,
83+
controllerBuilder.watchCRDs,
7984
controllerBuilder.watchGwClass,
8085
controllerBuilder.watchGw,
8186
controllerBuilder.watchHttpRoute,
@@ -292,21 +297,24 @@ func (c *controllerBuilder) watchGwClass(_ context.Context) error {
292297
Complete(reconcile.Func(c.reconciler.ReconcileGatewayClasses))
293298
}
294299

295-
func (c *controllerBuilder) watchCustomResourceDefinitions(_ context.Context) error {
300+
// watchCustomResourceDefinitions sets up a controller to watch for changes to specific Gateway API
301+
// CRDs and triggers GatewayClass reconciliation if generation or annotations change.
302+
func (c *controllerBuilder) watchCRDs(_ context.Context) error {
296303
return ctrl.NewControllerManagedBy(c.cfg.Mgr).
297-
WithEventFilter(predicate.And(
304+
For(&apiextv1.CustomResourceDefinition{}).
305+
WithEventFilter(predicate.NewPredicateFuncs(func(object client.Object) bool {
306+
crd, ok := object.(*apiextv1.CustomResourceDefinition)
307+
if !ok {
308+
return false
309+
}
310+
// Check if the CRD is one we care about
311+
return c.cfg.CRDs.Has(crd.Name)
312+
})).
313+
WithEventFilter(predicate.Or(
314+
predicate.AnnotationChangedPredicate{},
298315
predicate.GenerationChangedPredicate{},
299-
predicate.NewPredicateFuncs(func(object client.Object) bool {
300-
crd, ok := object.(*apiextensionsv1.CustomResourceDefinition)
301-
if !ok {
302-
return false
303-
}
304-
// Check if the CRD is one we care about
305-
return c.cfg.CRDs.Has(crd.Name)
306-
}),
307316
)).
308-
For(&apiextensionsv1.CustomResourceDefinition{}).
309-
Complete(reconcile.Func(c.reconciler.ReconcileCustomResourceDefinitions))
317+
Complete(reconcile.Func(c.reconciler.ReconcileCRDs))
310318
}
311319

312320
func (c *controllerBuilder) watchHttpRoute(_ context.Context) error {
@@ -412,9 +420,10 @@ func (c *controllerBuilder) watchSecrets(ctx context.Context) error {
412420
}
413421

414422
type controllerReconciler struct {
415-
cli client.Client
416-
scheme *runtime.Scheme
417-
kick func(ctx context.Context)
423+
controllerName string
424+
cli client.Client
425+
scheme *runtime.Scheme
426+
kick func(ctx context.Context)
418427
}
419428

420429
func (r *controllerReconciler) ReconcileHttpListenerOptions(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
@@ -491,12 +500,6 @@ func (r *controllerReconciler) ReconcileNamespaces(ctx context.Context, req ctrl
491500
return ctrl.Result{}, nil
492501
}
493502

494-
func (r *controllerReconciler) ReconcileCustomResourceDefinitions(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
495-
// For now, simply trigger the main reconciliation loop
496-
r.kick(ctx)
497-
return ctrl.Result{}, nil
498-
}
499-
500503
func (r *controllerReconciler) ReconcileHttpRoutes(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
501504
// TODO: consider finding impacted gateways and queue them
502505
// TODO: consider enabling this
@@ -525,8 +528,41 @@ func (r *controllerReconciler) ReconcileReferenceGrants(ctx context.Context, req
525528
return ctrl.Result{}, nil
526529
}
527530

531+
func (r *controllerReconciler) ReconcileCRDs(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
532+
log := log.FromContext(ctx).WithValues("CustomResourceDefinition", req.Name)
533+
534+
log.Info("reconciling CustomResourceDefinition")
535+
536+
var gatewayClassList apiv1.GatewayClassList
537+
if err := r.cli.List(ctx, &gatewayClassList); err != nil {
538+
log.Error(err, "Failed to list GatewayClasses")
539+
return ctrl.Result{}, err
540+
}
541+
542+
for _, gatewayClass := range gatewayClassList.Items {
543+
if gatewayClass.Spec.ControllerName == apiv1.GatewayController(r.controllerName) {
544+
req := ctrl.Request{
545+
NamespacedName: client.ObjectKey{
546+
Name: gatewayClass.Name,
547+
},
548+
}
549+
550+
if _, err := r.ReconcileGatewayClasses(ctx, req); err != nil {
551+
log.Error(err, "Failed to reconcile GatewayClass", "name", gatewayClass.Name)
552+
return ctrl.Result{}, err
553+
}
554+
}
555+
}
556+
557+
r.kick(ctx)
558+
559+
log.Info("Reconciled CustomResourceDefinition")
560+
561+
return ctrl.Result{}, nil
562+
}
563+
528564
func (r *controllerReconciler) ReconcileGatewayClasses(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
529-
log := log.FromContext(ctx).WithValues("gwclass", req.NamespacedName)
565+
log := log.FromContext(ctx).WithValues("GatewayClass", req.Name)
530566

531567
gwclass := &apiv1.GatewayClass{}
532568
if err := r.cli.Get(ctx, req.NamespacedName, gwclass); err != nil {
@@ -537,31 +573,74 @@ func (r *controllerReconciler) ReconcileGatewayClasses(ctx context.Context, req
537573
return ctrl.Result{}, client.IgnoreNotFound(err)
538574
}
539575

540-
log.Info("reconciling gateway class")
576+
log.Info("Reconciling GatewayClass")
541577

542-
// mark it as accepted:
578+
// Initialize the status conditions. No need to set LastTransitionTime since it's handled by SetStatusCondition.
579+
msg := "Gateway API CRDs are a supported version"
543580
acceptedCondition := metav1.Condition{
544581
Type: string(apiv1.GatewayClassConditionStatusAccepted),
545582
Status: metav1.ConditionTrue,
546583
Reason: string(apiv1.GatewayClassReasonAccepted),
584+
Message: msg,
547585
ObservedGeneration: gwclass.Generation,
548-
// no need to set LastTransitionTime, it will be set automatically by SetStatusCondition
549586
}
550-
meta.SetStatusCondition(&gwclass.Status.Conditions, acceptedCondition)
551-
552-
// TODO: This should actually check the version of the CRDs in the cluster to be 100% sure
553-
supportedVersionCondition := metav1.Condition{
587+
supportedCondition := metav1.Condition{
554588
Type: string(apiv1.GatewayClassConditionStatusSupportedVersion),
555589
Status: metav1.ConditionTrue,
556-
ObservedGeneration: gwclass.Generation,
557590
Reason: string(apiv1.GatewayClassReasonSupportedVersion),
591+
Message: msg,
592+
ObservedGeneration: gwclass.Generation,
593+
}
594+
595+
// Check CRD versions
596+
supported, err := r.checkCRDVersions(ctx)
597+
if err != nil {
598+
log.Error(err, "Failed to check CRD versions")
599+
return ctrl.Result{}, err
600+
}
601+
602+
if !supported {
603+
// Update the values of status conditions
604+
msg = fmt.Sprintf("Unsupported Gateway API CRDs detected. Supported versions are: %s",
605+
strings.Join(wellknown.SupportedVersions, ", "))
606+
acceptedCondition.Status = metav1.ConditionFalse
607+
acceptedCondition.Reason = string(apiv1.GatewayClassReasonUnsupportedVersion)
608+
acceptedCondition.Message = msg
609+
supportedCondition.Status = metav1.ConditionFalse
610+
supportedCondition.Reason = string(apiv1.GatewayClassReasonUnsupportedVersion)
611+
supportedCondition.Message = msg
558612
}
559-
meta.SetStatusCondition(&gwclass.Status.Conditions, supportedVersionCondition)
613+
614+
// Set the status conditions
615+
meta.SetStatusCondition(&gwclass.Status.Conditions, acceptedCondition)
616+
meta.SetStatusCondition(&gwclass.Status.Conditions, supportedCondition)
560617

561618
if err := r.cli.Status().Update(ctx, gwclass); err != nil {
619+
log.Error(err, "Failed to update GatewayClass status")
562620
return ctrl.Result{}, err
563621
}
564-
log.Info("updated gateway class status")
622+
623+
log.Info("Reconciled GatewayClass")
565624

566625
return ctrl.Result{}, nil
567626
}
627+
628+
// checkCRDVersions checks that the "gateway.networking.k8s.io/bundle-version" annotation key is set
629+
// to a supported version for each required Gateway API CRD.
630+
func (r *controllerReconciler) checkCRDVersions(ctx context.Context) (bool, error) {
631+
for _, crdName := range crds.Required {
632+
crd := &apiextv1.CustomResourceDefinition{}
633+
if err := r.cli.Get(ctx, client.ObjectKey{Name: crdName}, crd); err != nil {
634+
if kerrors.IsNotFound(err) {
635+
return false, nil
636+
}
637+
return false, err
638+
}
639+
640+
bundleVersion, exists := crd.Annotations[consts.BundleVersionAnnotation]
641+
if !exists || !crds.IsSupportedVersion(bundleVersion) {
642+
return false, nil
643+
}
644+
}
645+
return true, nil
646+
}

projects/gateway2/crds/crds.go

+32
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,39 @@ package crds
22

33
import (
44
_ "embed"
5+
6+
"k8s.io/apimachinery/pkg/util/sets"
7+
8+
"github.com/solo-io/gloo/projects/gateway2/wellknown"
9+
)
10+
11+
const (
12+
// GatewayClass is the name of the GatewayClass CRD.
13+
GatewayClass = "gatewayclasses.gateway.networking.k8s.io"
14+
// Gateway is the name of the Gateway CRD.
15+
Gateway = "gateways.gateway.networking.k8s.io"
16+
// HTTPRoute is the name of the HTTPRoute CRD.
17+
HTTPRoute = "httproutes.gateway.networking.k8s.io"
18+
// ReferenceGrant is the name of the ReferenceGrant CRD.
19+
ReferenceGrant = "referencegrants.gateway.networking.k8s.io"
520
)
621

722
//go:embed gateway-crds.yaml
823
var GatewayCrds []byte
24+
25+
// Required is a list of required Gateway API CRDs.
26+
var Required = []string{GatewayClass, Gateway, HTTPRoute, ReferenceGrant}
27+
28+
// IsSupportedVersion checks if the CRD version is recognized and supported.
29+
func IsSupportedVersion(version string) bool {
30+
supportedVersions := sets.NewString(wellknown.SupportedVersions...)
31+
return supportedVersions.Has(version)
32+
}
33+
34+
// IsSupported checks if the CRD is supported based on the provided name.
35+
func IsSupported(name string) bool {
36+
return name == GatewayClass ||
37+
name == Gateway ||
38+
name == HTTPRoute ||
39+
name == ReferenceGrant
40+
}

projects/gateway2/wellknown/gwapi.go

+4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ const (
4141
)
4242

4343
var (
44+
// SupportedVersions specifies the supported versions of Gateway API.
45+
// [TODO] Remove "v1.0.0-rc1" when https://github.com/solo-io/gloo/issues/10115 is fixed.
46+
SupportedVersions = []string{"v1.1.0", "v1.0.0", "v1.0.0-rc1"}
47+
4448
GatewayGVK = schema.GroupVersionKind{
4549
Group: GatewayGroup,
4650
Version: apiv1.GroupVersion.Version,

0 commit comments

Comments
 (0)