Skip to content

Commit 11d2536

Browse files
fix(replicator): fall back to storage version for downstream GVK
When a Gateway API type graduates (e.g. BackendTLSPolicy v1alpha3 → v1) and the downstream cluster stops serving the older version, registering a watch for the old version blocks controller-runtime's WaitForCacheSync and prevents the gateway controller from starting. Creating, fetching, and deleting downstream objects via the old version also fails. Resolve the downstream GVK once at setup time by checking whether the downstream cluster's REST mapper serves the configured version. If not, and a statusGVK (the storage version) is configured, use that instead. The resolved downstreamGVK is stored on replicationResource and used consistently across the watch registration, CreateOrUpdate, status fetch, and delete paths. The enqueue function still keys on group+kind so reconciliation fires correctly regardless of which served version is observed.
1 parent dc4a511 commit 11d2536

2 files changed

Lines changed: 24 additions & 7 deletions

File tree

internal/controller/gateway_resource_replicator_controller.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
corev1 "k8s.io/api/core/v1"
1212
apiequality "k8s.io/apimachinery/pkg/api/equality"
1313
apierrors "k8s.io/apimachinery/pkg/api/errors"
14+
apimeta "k8s.io/apimachinery/pkg/api/meta"
1415
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1516
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1617
"k8s.io/apimachinery/pkg/labels"
@@ -86,6 +87,7 @@ type replicationResourceConfig struct {
8687

8788
type replicationResource struct {
8889
gvk schema.GroupVersionKind
90+
downstreamGVK schema.GroupVersionKind
8991
replicationResourceConfig replicationResourceConfig
9092
controllerName string
9193
}
@@ -221,7 +223,7 @@ func (r *GatewayResourceReplicatorReconciler) Reconcile(ctx context.Context, req
221223
downstreamStrategy := downstreamclient.NewMappedNamespaceResourceStrategy(string(req.ClusterName), upstreamClient, r.DownstreamCluster.GetClient())
222224

223225
if !upstreamObj.GetDeletionTimestamp().IsZero() {
224-
return r.finalizeResource(ctx, upstreamClient, upstreamObj, downstreamStrategy)
226+
return r.finalizeResource(ctx, resourceCfg, upstreamClient, upstreamObj, downstreamStrategy)
225227
}
226228

227229
if !controllerutil.ContainsFinalizer(upstreamObj, gatewayResourceReplicatorFinalizer) {
@@ -245,12 +247,13 @@ func (r *GatewayResourceReplicatorReconciler) Reconcile(ctx context.Context, req
245247
// nolint:unparam
246248
func (r *GatewayResourceReplicatorReconciler) finalizeResource(
247249
ctx context.Context,
250+
resource replicationResource,
248251
upstreamClient client.Client,
249252
upstreamObj *unstructured.Unstructured,
250253
downstreamStrategy downstreamclient.ResourceStrategy,
251254
) (ctrl.Result, error) {
252255
if controllerutil.ContainsFinalizer(upstreamObj, gatewayResourceReplicatorFinalizer) {
253-
if err := r.finalize(ctx, upstreamObj, downstreamStrategy); err != nil {
256+
if err := r.finalize(ctx, resource, upstreamObj, downstreamStrategy); err != nil {
254257
return ctrl.Result{}, err
255258
}
256259

@@ -292,7 +295,7 @@ func (r *GatewayResourceReplicatorReconciler) ensureDownstreamResource(
292295
}
293296

294297
downstreamObj := &unstructured.Unstructured{}
295-
downstreamObj.SetGroupVersionKind(upstreamObj.GroupVersionKind())
298+
downstreamObj.SetGroupVersionKind(resource.downstreamGVK)
296299
downstreamObj.SetName(downstreamObjectMeta.Name)
297300
downstreamObj.SetNamespace(downstreamObjectMeta.Namespace)
298301

@@ -434,7 +437,7 @@ func (r *GatewayResourceReplicatorReconciler) syncUpstreamStatus(
434437
) error {
435438
logger := log.FromContext(ctx)
436439
downstreamObj := &unstructured.Unstructured{}
437-
downstreamObj.SetGroupVersionKind(upstreamObj.GroupVersionKind())
440+
downstreamObj.SetGroupVersionKind(resource.downstreamGVK)
438441

439442
if err := downstreamStrategy.GetClient().Get(ctx, client.ObjectKey{Name: downstreamMeta.Name, Namespace: downstreamMeta.Namespace}, downstreamObj); err != nil {
440443
if apierrors.IsNotFound(err) {
@@ -671,6 +674,7 @@ func defaultGatewayEnvoyReasonHandlers() conditionReasonHandlers {
671674

672675
func (r *GatewayResourceReplicatorReconciler) finalize(
673676
ctx context.Context,
677+
resource replicationResource,
674678
upstreamObj *unstructured.Unstructured,
675679
downstreamStrategy downstreamclient.ResourceStrategy,
676680
) error {
@@ -681,7 +685,7 @@ func (r *GatewayResourceReplicatorReconciler) finalize(
681685
}
682686

683687
downstreamObj := &unstructured.Unstructured{}
684-
downstreamObj.SetGroupVersionKind(upstreamObj.GroupVersionKind())
688+
downstreamObj.SetGroupVersionKind(resource.downstreamGVK)
685689
downstreamObj.SetName(downstreamObjectMeta.Name)
686690
downstreamObj.SetNamespace(downstreamObjectMeta.Namespace)
687691

@@ -728,19 +732,32 @@ func (r *GatewayResourceReplicatorReconciler) SetupWithManager(mgr mcmanager.Man
728732

729733
resource := replicationResource{
730734
gvk: gvk,
735+
downstreamGVK: gvk,
731736
controllerName: string(r.Config.Gateway.ControllerName),
732737
}
733738
if cfg, ok := defaultReplicationResourceConfigs[gvkKey(gvk)]; ok {
734739
resource.replicationResourceConfig = cfg
735740
}
736741

742+
// If the downstream cluster no longer serves the upstream version (e.g.
743+
// v1alpha3 removed after a Gateway API graduation), fall back to the
744+
// storage version for both the watch and the create path. The discovery
745+
// check is done once at setup so reconciles pay no extra cost.
746+
if resource.replicationResourceConfig.statusGVK != nil {
747+
if _, err := r.DownstreamCluster.GetRESTMapper().RESTMapping(
748+
schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version,
749+
); err != nil && apimeta.IsNoMatchError(err) {
750+
resource.downstreamGVK = *resource.replicationResourceConfig.statusGVK
751+
}
752+
}
753+
737754
resources[gvkKey(gvk)] = resource
738755

739756
upstreamWatchObj := newUnstructuredForGVK(gvk)
740757

741758
builder = builder.Watches(upstreamWatchObj, typedEnqueueRequestForGVK(gvk, selector))
742759

743-
downstreamWatchObj := newUnstructuredForGVK(gvk)
760+
downstreamWatchObj := newUnstructuredForGVK(resource.downstreamGVK)
744761

745762
src := mcsource.TypedKind(
746763
downstreamWatchObj,

internal/controller/gateway_resource_replicator_controller_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,7 @@ func newReplicatorForGVKTest(gvk schema.GroupVersionKind, upstream client.Client
625625
upstreamCluster := &replicatorFakeCluster{scheme: scheme, c: upstream}
626626
downstreamCluster := &replicatorFakeCluster{scheme: scheme, c: downstream}
627627

628-
resource := replicationResource{gvk: gvk, controllerName: testControllerName}
628+
resource := replicationResource{gvk: gvk, downstreamGVK: gvk, controllerName: testControllerName}
629629
if cfg, ok := defaultReplicationResourceConfigs[gvkKey(gvk)]; ok {
630630
resource.replicationResourceConfig = cfg
631631
}

0 commit comments

Comments
 (0)