From b30b995f0d7d6f6754536faa0345c6c9759dea5a Mon Sep 17 00:00:00 2001 From: Dustin Row Date: Wed, 13 May 2026 13:52:50 -0700 Subject: [PATCH] fix: remove stale api.openshift.com labels from HostedControlPlane OCPBUGS-85584 The label sync from HostedCluster to HostedControlPlane was additive-only. When OCM removes a label from HC (e.g., clearing limited support), the label was never removed from HCP. This caused downstream consumers like Route Monitor Operator to permanently skip probe creation for fully supported clusters. Fix: before copying HC labels to HCP, remove any api.openshift.com/* labels from HCP that no longer exist on HC. --- .../hostedcluster/hostedcluster_controller.go | 15 ++++- .../hostedcluster_controller_test.go | 58 +++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go b/hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go index eeb4378fed2..ccd490e9b0b 100644 --- a/hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go +++ b/hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go @@ -135,6 +135,8 @@ const ( useRestrictedPodSecurityLabel = "io.openshift.hypershift.restricted-psa" defaultToControlPlaneV2Label = "io.openshift.hypershift.control-plane-operator.v2-isdefault" + apiOpenShiftComLabelPrefix = "api.openshift.com/" + etcdEncKeyPostfix = "-etcd-encryption-key" jobHostedClusterNameLabel = "hypershift.openshift.io/cluster-name" @@ -2423,10 +2425,17 @@ func reconcileHostedControlPlane(hcp *hyperv1.HostedControlPlane, hcluster *hype if hcp.Labels == nil { hcp.Labels = make(map[string]string) } - // All labels on the HostedCluster with this special prefix are copied - // Those are labels set by OCM + // These labels are managed by OCM. Delete-then-copy ensures removals + // on the HostedCluster (e.g., clearing limited-support) propagate to the HCP. + for key := range hcp.Labels { + if strings.HasPrefix(key, apiOpenShiftComLabelPrefix) { + if _, exists := hcluster.Labels[key]; !exists { + delete(hcp.Labels, key) + } + } + } for key, val := range hcluster.Labels { - if strings.HasPrefix(key, "api.openshift.com") { + if strings.HasPrefix(key, apiOpenShiftComLabelPrefix) { hcp.Labels[key] = val } } diff --git a/hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go b/hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go index f289f3dd122..073897aa5d5 100644 --- a/hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go +++ b/hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go @@ -310,6 +310,64 @@ func TestReconcileHostedControlPlaneAdditionalTrustBundle(t *testing.T) { } } +func TestReconcileHostedControlPlaneLabelSync(t *testing.T) { + t.Parallel() + tests := []struct { + name string + hcLabels map[string]string + hcpLabels map[string]string + expectedLabels map[string]string + }{ + { + name: "When HC has api.openshift.com labels, it should copy them to HCP", + hcLabels: map[string]string{"api.openshift.com/limited-support": "true", "api.openshift.com/name": "test"}, + hcpLabels: map[string]string{}, + expectedLabels: map[string]string{"api.openshift.com/limited-support": "true", "api.openshift.com/name": "test"}, + }, + { + name: "When HC removes an api.openshift.com label, it should remove the stale label from HCP", + hcLabels: map[string]string{"api.openshift.com/name": "test"}, + hcpLabels: map[string]string{"api.openshift.com/limited-support": "true", "api.openshift.com/name": "old"}, + expectedLabels: map[string]string{"api.openshift.com/name": "test"}, + }, + { + name: "When HC has no api.openshift.com labels, it should preserve non-api.openshift.com labels on HCP", + hcLabels: map[string]string{}, + hcpLabels: map[string]string{"api.openshift.com/limited-support": "true", "cluster.x-k8s.io/cluster-name": "keep-me"}, + expectedLabels: map[string]string{"cluster.x-k8s.io/cluster-name": "keep-me"}, + }, + { + name: "When HC labels are nil, it should remove all api.openshift.com labels from HCP", + hcLabels: nil, + hcpLabels: map[string]string{"api.openshift.com/limited-support": "true"}, + expectedLabels: map[string]string{}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + g := NewGomegaWithT(t) + hc := &hyperv1.HostedCluster{ + ObjectMeta: metav1.ObjectMeta{Labels: test.hcLabels}, + } + hcp := &hyperv1.HostedControlPlane{ + ObjectMeta: metav1.ObjectMeta{Labels: test.hcpLabels}, + } + err := reconcileHostedControlPlane(hcp, hc, false, false, func() (map[string]string, error) { return nil, nil }) + g.Expect(err).ToNot(HaveOccurred()) + + for key, val := range test.expectedLabels { + g.Expect(hcp.Labels).To(HaveKeyWithValue(key, val)) + } + for key := range hcp.Labels { + if strings.HasPrefix(key, apiOpenShiftComLabelPrefix) { + g.Expect(test.expectedLabels).To(HaveKey(key), "unexpected label %s=%s still on HCP", key, hcp.Labels[key]) + } + } + }) + } +} + func TestReconcileHostedControlPlaneUpgrades(t *testing.T) { t.Parallel() // TODO: the spec/status comparison of control plane is a weak check; the