From b1ac6cd8e25d851735a08180e5f5c23c105df492 Mon Sep 17 00:00:00 2001 From: gangwgr Date: Tue, 19 May 2026 16:50:14 +0530 Subject: [PATCH 1/7] tls: fix HyperShift guest-side CO wait and Custom profile handling Fix two issues found in HyperShift CI: 1. waitForGuestOperatorsAfterTLSChange was iterating over all clusterOperatorNames (including etcd, kube-apiserver, etc.) which run on the management cluster. On HyperShift these COs may not stabilize on the guest side after a TLS change, causing a 25m timeout. Use a new guestSideClusterOperatorNames list that only includes guest-side COs (image-registry, openshift-samples). 2. getExpectedMinTLSVersionWithType crashed with "Unknown TLS profile type: Custom" because configv1.TLSProfiles only contains predefined profiles. If a Custom profile is active (e.g. from a failed cleanup), read minTLSVersion directly from the Custom spec instead. --- test/extended/tls/tls_observed_config.go | 26 +++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/test/extended/tls/tls_observed_config.go b/test/extended/tls/tls_observed_config.go index c9cff81cd996..230196d32720 100644 --- a/test/extended/tls/tls_observed_config.go +++ b/test/extended/tls/tls_observed_config.go @@ -147,6 +147,15 @@ var clusterOperatorNames = []string{ "openshift-samples", } +// guestSideClusterOperatorNames lists ClusterOperators that run on the guest +// cluster (not the management-cluster control plane). On HyperShift, +// control-plane COs (etcd, kube-apiserver, etc.) are managed by the HCP +// and should not be polled from the guest side after a TLS change. +var guestSideClusterOperatorNames = []string{ + "image-registry", + "openshift-samples", +} + var deploymentRolloutTargets = []deploymentRolloutTarget{ {namespace: "openshift-image-registry", deploymentName: "image-registry"}, {namespace: "openshift-controller-manager", deploymentName: "controller-manager"}, @@ -1334,12 +1343,19 @@ func getExpectedMinTLSVersionWithType(oc *exutil.CLI, ctx context.Context) (stri profileType = config.Spec.TLSSecurityProfile.Type } - profile, ok := configv1.TLSProfiles[profileType] - if !ok { - e2e.Failf("Unknown TLS profile type: %s", profileType) + var minVersion string + if profileType == configv1.TLSProfileCustomType { + o.Expect(config.Spec.TLSSecurityProfile.Custom).NotTo(o.BeNil(), + "Custom TLS profile set but .custom spec is nil") + minVersion = string(config.Spec.TLSSecurityProfile.Custom.MinTLSVersion) + } else { + profile, ok := configv1.TLSProfiles[profileType] + if !ok { + e2e.Failf("Unknown TLS profile type: %s", profileType) + } + minVersion = string(profile.MinTLSVersion) } - minVersion := string(profile.MinTLSVersion) profileName := string(profileType) if profileType == "" || profileType == configv1.TLSProfileIntermediateType { profileName = "Intermediate (default)" @@ -1760,7 +1776,7 @@ func waitForHCPAppReady(mgmtCLI *exutil.CLI, appLabel, hcpNS string, timeout tim // and Deployments to stabilize after a TLS profile change on HyperShift. func waitForGuestOperatorsAfterTLSChange(oc *exutil.CLI, ctx context.Context, profileLabel string, rollouts []deploymentRolloutTarget) { e2e.Logf("Waiting for guest-side ClusterOperators to stabilize after %s profile change", profileLabel) - for _, co := range clusterOperatorNames { + for _, co := range guestSideClusterOperatorNames { e2e.Logf("Waiting for ClusterOperator %s to stabilize after %s switch", co, profileLabel) waitForClusterOperatorStable(oc, ctx, co) } From 212f6282c5506606283c3dce5db42964b4384832 Mon Sep 17 00:00:00 2001 From: gangwgr Date: Tue, 19 May 2026 21:40:39 +0530 Subject: [PATCH 2/7] tls: filter control-plane deployments from guest-side rollout targets On HyperShift, control-plane deployments like controller-manager and apiserver do not exist on the guest cluster. Add controlPlane field to deploymentRolloutTarget and filter them out in guestSideDeploymentRolloutTargets() to prevent 404 errors during post-TLS-change rollout verification. --- test/extended/tls/tls_observed_config.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/test/extended/tls/tls_observed_config.go b/test/extended/tls/tls_observed_config.go index 230196d32720..bc3500514b05 100644 --- a/test/extended/tls/tls_observed_config.go +++ b/test/extended/tls/tls_observed_config.go @@ -90,6 +90,7 @@ type serviceTarget struct { type deploymentRolloutTarget struct { namespace string deploymentName string + controlPlane bool } // ─── Typed target lists ──────────────────────────────────────────────────── @@ -158,9 +159,9 @@ var guestSideClusterOperatorNames = []string{ var deploymentRolloutTargets = []deploymentRolloutTarget{ {namespace: "openshift-image-registry", deploymentName: "image-registry"}, - {namespace: "openshift-controller-manager", deploymentName: "controller-manager"}, - {namespace: "openshift-apiserver", deploymentName: "apiserver"}, - {namespace: "openshift-cluster-version", deploymentName: "cluster-version-operator"}, + {namespace: "openshift-controller-manager", deploymentName: "controller-manager", controlPlane: true}, + {namespace: "openshift-apiserver", deploymentName: "apiserver", controlPlane: true}, + {namespace: "openshift-cluster-version", deploymentName: "cluster-version-operator", controlPlane: true}, {namespace: "openshift-cluster-samples-operator", deploymentName: "cluster-samples-operator"}, } @@ -206,11 +207,14 @@ func guestSideServiceTargets() []serviceTarget { return result } -// guestSideDeploymentRolloutTargets returns all deployment rollout targets. -// deploymentRolloutTarget has no controlPlane field because all rollout -// targets are accessible from the guest cluster. func guestSideDeploymentRolloutTargets() []deploymentRolloutTarget { - return deploymentRolloutTargets + var result []deploymentRolloutTarget + for _, t := range deploymentRolloutTargets { + if !t.controlPlane { + result = append(result, t) + } + } + return result } // ── read-only tests ──────────────────────────────────────────── From 0a0131e908ce1b68f32c761fe5a07112d0f11d14 Mon Sep 17 00:00:00 2001 From: gangwgr Date: Wed, 20 May 2026 09:54:23 +0530 Subject: [PATCH 3/7] tls: poll ConfigMaps for TLS version after profile switch On HyperShift, guest-side operators and deployments stabilize quickly but ConfigMap TLS annotation injection can lag. Replace the one-shot check in verifyConfigMapsForTargets with polling (5s interval, 5m timeout) so the test waits for propagation rather than failing immediately. --- test/extended/tls/tls_observed_config.go | 34 +++++++++++++++++------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/test/extended/tls/tls_observed_config.go b/test/extended/tls/tls_observed_config.go index bc3500514b05..821d496961ae 100644 --- a/test/extended/tls/tls_observed_config.go +++ b/test/extended/tls/tls_observed_config.go @@ -1311,18 +1311,32 @@ func verifyConfigMapsAfterSwitch(oc *exutil.CLI, ctx context.Context, expectedVe // verifyConfigMapsForTargets checks a specific list of targets for // ConfigMap TLS injection correctness after a TLS profile switch. +// It polls each ConfigMap for up to 5 minutes because the TLS annotation +// injection can lag behind operator/deployment stabilization. func verifyConfigMapsForTargets(oc *exutil.CLI, ctx context.Context, expectedVersion, profileLabel string, targetList []configMapTarget) { for _, t := range targetList { - cm, err := oc.AdminKubeClient().CoreV1().ConfigMaps(t.configMapNamespace).Get(ctx, t.configMapName, metav1.GetOptions{}) - if err != nil { - e2e.Logf("SKIP: ConfigMap %s/%s not found: %v", t.configMapNamespace, t.configMapName, err) - continue - } - configData := cm.Data[t.configMapKey] - o.Expect(cm.Annotations).To(o.HaveKey(injectTLSAnnotation), - fmt.Sprintf("ConfigMap %s/%s is missing %s annotation", t.configMapNamespace, t.configMapName, injectTLSAnnotation)) - o.Expect(configData).To(o.ContainSubstring(expectedVersion), - fmt.Sprintf("ConfigMap %s/%s should have %s after %s switch", + e2e.Logf("Waiting for ConfigMap %s/%s to reflect %s after %s switch", + t.configMapNamespace, t.configMapName, expectedVersion, profileLabel) + err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 5*time.Minute, true, + func(ctx context.Context) (bool, error) { + cm, err := oc.AdminKubeClient().CoreV1().ConfigMaps(t.configMapNamespace).Get(ctx, t.configMapName, metav1.GetOptions{}) + if err != nil { + e2e.Logf(" poll: ConfigMap %s/%s not found: %v", t.configMapNamespace, t.configMapName, err) + return false, nil + } + if _, ok := cm.Annotations[injectTLSAnnotation]; !ok { + e2e.Logf(" poll: ConfigMap %s/%s missing %s annotation", t.configMapNamespace, t.configMapName, injectTLSAnnotation) + return false, nil + } + configData := cm.Data[t.configMapKey] + if !strings.Contains(configData, expectedVersion) { + e2e.Logf(" poll: ConfigMap %s/%s does not yet contain %s", t.configMapNamespace, t.configMapName, expectedVersion) + return false, nil + } + return true, nil + }) + o.Expect(err).NotTo(o.HaveOccurred(), + fmt.Sprintf("ConfigMap %s/%s did not contain %s within 5 minutes after %s switch", t.configMapNamespace, t.configMapName, expectedVersion, profileLabel)) e2e.Logf("PASS: ConfigMap %s/%s has %s after %s switch", t.configMapNamespace, t.configMapName, expectedVersion, profileLabel) From daa8f97edb8d06da62cde3ede63e206125992177 Mon Sep 17 00:00:00 2001 From: gangwgr Date: Wed, 20 May 2026 16:21:22 +0530 Subject: [PATCH 4/7] tls: rename controlPlane to managementClusterComponent Rename the controlPlane field across all target structs to managementClusterComponent to better convey that the flag indicates whether a component runs on the management cluster (and should be skipped when testing from the guest cluster context on HyperShift). --- test/extended/tls/tls_observed_config.go | 115 +++++++++++------------ 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/test/extended/tls/tls_observed_config.go b/test/extended/tls/tls_observed_config.go index 821d496961ae..34e1f6b1d14f 100644 --- a/test/extended/tls/tls_observed_config.go +++ b/test/extended/tls/tls_observed_config.go @@ -50,47 +50,47 @@ const ( // observedConfigTarget identifies an operator whose spec.observedConfig // must contain servingInfo with minTLSVersion and cipherSuites. type observedConfigTarget struct { - namespace string - operatorConfigGVR schema.GroupVersionResource - operatorConfigName string - controlPlane bool + namespace string + operatorConfigGVR schema.GroupVersionResource + operatorConfigName string + managementClusterComponent bool } // configMapTarget identifies a ConfigMap that CVO injects TLS config into. type configMapTarget struct { - namespace string // workload namespace (used in test names) - configMapName string - configMapNamespace string // namespace where the ConfigMap lives - configMapKey string // data key within the ConfigMap - controlPlane bool + namespace string // workload namespace (used in test names) + configMapName string + configMapNamespace string // namespace where the ConfigMap lives + configMapKey string // data key within the ConfigMap + managementClusterComponent bool } // deploymentEnvVarTarget identifies a Deployment whose containers must // have TLS-related environment variables matching the cluster profile. type deploymentEnvVarTarget struct { - namespace string - deploymentName string - tlsMinVersionEnvVar string - cipherSuitesEnvVar string - controlPlane bool + namespace string + deploymentName string + tlsMinVersionEnvVar string + cipherSuitesEnvVar string + managementClusterComponent bool } // serviceTarget identifies a Service endpoint that must enforce the // cluster TLS profile at the wire level. type serviceTarget struct { - namespace string - serviceName string - servicePort string - deploymentName string // for waiting on rollout before probing - controlPlane bool + namespace string + serviceName string + servicePort string + deploymentName string // for waiting on rollout before probing + managementClusterComponent bool } // deploymentRolloutTarget identifies a Deployment that must complete // rollout after a TLS profile change. type deploymentRolloutTarget struct { - namespace string - deploymentName string - controlPlane bool + namespace string + deploymentName string + managementClusterComponent bool } // ─── Typed target lists ──────────────────────────────────────────────────── @@ -99,12 +99,12 @@ type deploymentRolloutTarget struct { var observedConfigTargets = []observedConfigTarget{ {namespace: "openshift-image-registry", operatorConfigGVR: schema.GroupVersionResource{Group: "imageregistry.operator.openshift.io", Version: "v1", Resource: "configs"}, operatorConfigName: "cluster"}, - {namespace: "openshift-controller-manager", operatorConfigGVR: schema.GroupVersionResource{Group: "operator.openshift.io", Version: "v1", Resource: "openshiftcontrollermanagers"}, operatorConfigName: "cluster", controlPlane: true}, - {namespace: "openshift-kube-apiserver", operatorConfigGVR: schema.GroupVersionResource{Group: "operator.openshift.io", Version: "v1", Resource: "kubeapiservers"}, operatorConfigName: "cluster", controlPlane: true}, - {namespace: "openshift-apiserver", operatorConfigGVR: schema.GroupVersionResource{Group: "operator.openshift.io", Version: "v1", Resource: "openshiftapiservers"}, operatorConfigName: "cluster", controlPlane: true}, - {namespace: "openshift-etcd", operatorConfigGVR: schema.GroupVersionResource{Group: "operator.openshift.io", Version: "v1", Resource: "etcds"}, operatorConfigName: "cluster", controlPlane: true}, - {namespace: "openshift-kube-controller-manager", operatorConfigGVR: schema.GroupVersionResource{Group: "operator.openshift.io", Version: "v1", Resource: "kubecontrollermanagers"}, operatorConfigName: "cluster", controlPlane: true}, - {namespace: "openshift-kube-scheduler", operatorConfigGVR: schema.GroupVersionResource{Group: "operator.openshift.io", Version: "v1", Resource: "kubeschedulers"}, operatorConfigName: "cluster", controlPlane: true}, + {namespace: "openshift-controller-manager", operatorConfigGVR: schema.GroupVersionResource{Group: "operator.openshift.io", Version: "v1", Resource: "openshiftcontrollermanagers"}, operatorConfigName: "cluster", managementClusterComponent: true}, + {namespace: "openshift-kube-apiserver", operatorConfigGVR: schema.GroupVersionResource{Group: "operator.openshift.io", Version: "v1", Resource: "kubeapiservers"}, operatorConfigName: "cluster", managementClusterComponent: true}, + {namespace: "openshift-apiserver", operatorConfigGVR: schema.GroupVersionResource{Group: "operator.openshift.io", Version: "v1", Resource: "openshiftapiservers"}, operatorConfigName: "cluster", managementClusterComponent: true}, + {namespace: "openshift-etcd", operatorConfigGVR: schema.GroupVersionResource{Group: "operator.openshift.io", Version: "v1", Resource: "etcds"}, operatorConfigName: "cluster", managementClusterComponent: true}, + {namespace: "openshift-kube-controller-manager", operatorConfigGVR: schema.GroupVersionResource{Group: "operator.openshift.io", Version: "v1", Resource: "kubecontrollermanagers"}, operatorConfigName: "cluster", managementClusterComponent: true}, + {namespace: "openshift-kube-scheduler", operatorConfigGVR: schema.GroupVersionResource{Group: "operator.openshift.io", Version: "v1", Resource: "kubeschedulers"}, operatorConfigName: "cluster", managementClusterComponent: true}, } var configMapTargets = []configMapTarget{ @@ -112,7 +112,7 @@ var configMapTargets = []configMapTarget{ {namespace: "openshift-controller-manager", configMapName: "openshift-controller-manager-operator-config", configMapNamespace: "openshift-controller-manager-operator", configMapKey: "config.yaml"}, {namespace: "openshift-kube-apiserver", configMapName: "kube-apiserver-operator-config", configMapNamespace: "openshift-kube-apiserver-operator", configMapKey: "config.yaml"}, {namespace: "openshift-apiserver", configMapName: "openshift-apiserver-operator-config", configMapNamespace: "openshift-apiserver-operator", configMapKey: "config.yaml"}, - {namespace: "openshift-etcd", configMapName: "etcd-operator-config", configMapNamespace: "openshift-etcd-operator", configMapKey: "config.yaml", controlPlane: true}, + {namespace: "openshift-etcd", configMapName: "etcd-operator-config", configMapNamespace: "openshift-etcd-operator", configMapKey: "config.yaml", managementClusterComponent: true}, {namespace: "openshift-kube-controller-manager", configMapName: "kube-controller-manager-operator-config", configMapNamespace: "openshift-kube-controller-manager-operator", configMapKey: "config.yaml"}, {namespace: "openshift-kube-scheduler", configMapName: "openshift-kube-scheduler-operator-config", configMapNamespace: "openshift-kube-scheduler-operator", configMapKey: "config.yaml"}, {namespace: "openshift-cluster-samples-operator", configMapName: "samples-operator-config", configMapNamespace: "openshift-cluster-samples-operator", configMapKey: "config.yaml"}, @@ -124,15 +124,15 @@ var deploymentEnvVarTargets = []deploymentEnvVarTarget{ var serviceTargets = []serviceTarget{ {namespace: "openshift-image-registry", serviceName: "image-registry", servicePort: "5000", deploymentName: "image-registry"}, - {namespace: "openshift-image-registry", serviceName: "image-registry-operator", servicePort: "60000", controlPlane: true}, - {namespace: "openshift-controller-manager", serviceName: "controller-manager", servicePort: "443", deploymentName: "controller-manager", controlPlane: true}, - {namespace: "openshift-kube-apiserver", serviceName: "apiserver", servicePort: "443", controlPlane: true}, - {namespace: "openshift-kube-apiserver", serviceName: "apiserver", servicePort: "17697", controlPlane: true}, - {namespace: "openshift-apiserver", serviceName: "api", servicePort: "443", deploymentName: "apiserver", controlPlane: true}, - {namespace: "openshift-apiserver", serviceName: "check-endpoints", servicePort: "17698", controlPlane: true}, - {namespace: "openshift-etcd", serviceName: "etcd", servicePort: "2379", controlPlane: true}, - {namespace: "openshift-kube-controller-manager", serviceName: "kube-controller-manager", servicePort: "443", controlPlane: true}, - {namespace: "openshift-kube-scheduler", serviceName: "scheduler", servicePort: "443", controlPlane: true}, + {namespace: "openshift-image-registry", serviceName: "image-registry-operator", servicePort: "60000", managementClusterComponent: true}, + {namespace: "openshift-controller-manager", serviceName: "controller-manager", servicePort: "443", deploymentName: "controller-manager", managementClusterComponent: true}, + {namespace: "openshift-kube-apiserver", serviceName: "apiserver", servicePort: "443", managementClusterComponent: true}, + {namespace: "openshift-kube-apiserver", serviceName: "apiserver", servicePort: "17697", managementClusterComponent: true}, + {namespace: "openshift-apiserver", serviceName: "api", servicePort: "443", deploymentName: "apiserver", managementClusterComponent: true}, + {namespace: "openshift-apiserver", serviceName: "check-endpoints", servicePort: "17698", managementClusterComponent: true}, + {namespace: "openshift-etcd", serviceName: "etcd", servicePort: "2379", managementClusterComponent: true}, + {namespace: "openshift-kube-controller-manager", serviceName: "kube-controller-manager", servicePort: "443", managementClusterComponent: true}, + {namespace: "openshift-kube-scheduler", serviceName: "scheduler", servicePort: "443", managementClusterComponent: true}, {namespace: "openshift-cluster-samples-operator", serviceName: "metrics", servicePort: "60000", deploymentName: "cluster-samples-operator"}, } @@ -148,10 +148,9 @@ var clusterOperatorNames = []string{ "openshift-samples", } -// guestSideClusterOperatorNames lists ClusterOperators that run on the guest -// cluster (not the management-cluster control plane). On HyperShift, -// control-plane COs (etcd, kube-apiserver, etc.) are managed by the HCP -// and should not be polled from the guest side after a TLS change. +// guestSideClusterOperatorNames lists ClusterOperators that exist on the +// guest cluster. On HyperShift, management-cluster components (etcd, +// kube-apiserver, etc.) have no CO on the guest side and must be skipped. var guestSideClusterOperatorNames = []string{ "image-registry", "openshift-samples", @@ -159,9 +158,9 @@ var guestSideClusterOperatorNames = []string{ var deploymentRolloutTargets = []deploymentRolloutTarget{ {namespace: "openshift-image-registry", deploymentName: "image-registry"}, - {namespace: "openshift-controller-manager", deploymentName: "controller-manager", controlPlane: true}, - {namespace: "openshift-apiserver", deploymentName: "apiserver", controlPlane: true}, - {namespace: "openshift-cluster-version", deploymentName: "cluster-version-operator", controlPlane: true}, + {namespace: "openshift-controller-manager", deploymentName: "controller-manager", managementClusterComponent: true}, + {namespace: "openshift-apiserver", deploymentName: "apiserver", managementClusterComponent: true}, + {namespace: "openshift-cluster-version", deploymentName: "cluster-version-operator", managementClusterComponent: true}, {namespace: "openshift-cluster-samples-operator", deploymentName: "cluster-samples-operator"}, } @@ -170,7 +169,7 @@ var deploymentRolloutTargets = []deploymentRolloutTarget{ func guestSideObservedConfigTargets() []observedConfigTarget { var result []observedConfigTarget for _, t := range observedConfigTargets { - if !t.controlPlane { + if !t.managementClusterComponent { result = append(result, t) } } @@ -180,7 +179,7 @@ func guestSideObservedConfigTargets() []observedConfigTarget { func guestSideConfigMapTargets() []configMapTarget { var result []configMapTarget for _, t := range configMapTargets { - if !t.controlPlane { + if !t.managementClusterComponent { result = append(result, t) } } @@ -190,7 +189,7 @@ func guestSideConfigMapTargets() []configMapTarget { func guestSideDeploymentEnvVarTargets() []deploymentEnvVarTarget { var result []deploymentEnvVarTarget for _, t := range deploymentEnvVarTargets { - if !t.controlPlane { + if !t.managementClusterComponent { result = append(result, t) } } @@ -200,7 +199,7 @@ func guestSideDeploymentEnvVarTargets() []deploymentEnvVarTarget { func guestSideServiceTargets() []serviceTarget { var result []serviceTarget for _, t := range serviceTargets { - if !t.controlPlane { + if !t.managementClusterComponent { result = append(result, t) } } @@ -210,7 +209,7 @@ func guestSideServiceTargets() []serviceTarget { func guestSideDeploymentRolloutTargets() []deploymentRolloutTarget { var result []deploymentRolloutTarget for _, t := range deploymentRolloutTargets { - if !t.controlPlane { + if !t.managementClusterComponent { result = append(result, t) } } @@ -243,7 +242,7 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Suite for _, target := range observedConfigTargets { target := target g.It(fmt.Sprintf("should populate ObservedConfig with TLS settings - %s", target.namespace), func() { - if isHyperShiftCluster && target.controlPlane { + if isHyperShiftCluster && target.managementClusterComponent { g.Skip(fmt.Sprintf("Skipping control-plane target %s on HyperShift (runs on management cluster)", target.namespace)) } testObservedConfig(oc, ctx, target) @@ -254,7 +253,7 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Suite for _, target := range configMapTargets { target := target g.It(fmt.Sprintf("should have TLS config injected into ConfigMap - %s", target.namespace), func() { - if isHyperShiftCluster && target.controlPlane { + if isHyperShiftCluster && target.managementClusterComponent { g.Skip(fmt.Sprintf("Skipping control-plane target %s on HyperShift (runs on management cluster)", target.namespace)) } testConfigMapTLSInjection(oc, ctx, target) @@ -265,7 +264,7 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Suite for _, target := range deploymentEnvVarTargets { target := target g.It(fmt.Sprintf("should propagate TLS config to deployment env vars - %s", target.namespace), func() { - if isHyperShiftCluster && target.controlPlane { + if isHyperShiftCluster && target.managementClusterComponent { g.Skip(fmt.Sprintf("Skipping control-plane target %s on HyperShift (runs on management cluster)", target.namespace)) } testDeploymentTLSEnvVars(oc, ctx, target) @@ -276,7 +275,7 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Suite for _, target := range serviceTargets { target := target g.It(fmt.Sprintf("should enforce TLS version at the wire level - %s:%s", target.namespace, target.servicePort), func() { - if isHyperShiftCluster && target.controlPlane { + if isHyperShiftCluster && target.managementClusterComponent { g.Skip(fmt.Sprintf("Skipping control-plane target %s:%s on HyperShift (runs on management cluster)", target.namespace, target.servicePort)) } testWireLevelTLS(oc, ctx, target) @@ -340,28 +339,28 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Disru target := target g.It(fmt.Sprintf("should restore inject-tls annotation after deletion - %s", target.namespace), func() { - if isHyperShiftCluster && target.controlPlane { + if isHyperShiftCluster && target.managementClusterComponent { g.Skip(fmt.Sprintf("Skipping control-plane target %s on HyperShift (runs on management cluster)", target.namespace)) } testAnnotationRestorationAfterDeletion(oc, ctx, target) }) g.It(fmt.Sprintf("should restore inject-tls annotation when set to false - %s", target.namespace), func() { - if isHyperShiftCluster && target.controlPlane { + if isHyperShiftCluster && target.managementClusterComponent { g.Skip(fmt.Sprintf("Skipping control-plane target %s on HyperShift (runs on management cluster)", target.namespace)) } testAnnotationRestorationWhenFalse(oc, ctx, target) }) g.It(fmt.Sprintf("should restore servingInfo after removal - %s", target.namespace), func() { - if isHyperShiftCluster && target.controlPlane { + if isHyperShiftCluster && target.managementClusterComponent { g.Skip(fmt.Sprintf("Skipping control-plane target %s on HyperShift (runs on management cluster)", target.namespace)) } testServingInfoRestorationAfterRemoval(oc, ctx, target) }) g.It(fmt.Sprintf("should restore servingInfo after modification - %s", target.namespace), func() { - if isHyperShiftCluster && target.controlPlane { + if isHyperShiftCluster && target.managementClusterComponent { g.Skip(fmt.Sprintf("Skipping control-plane target %s on HyperShift (runs on management cluster)", target.namespace)) } testServingInfoRestorationAfterModification(oc, ctx, target) From 9590cbb332c51cc2969172343767f010af227533 Mon Sep 17 00:00:00 2001 From: gangwgr Date: Wed, 20 May 2026 16:33:55 +0530 Subject: [PATCH 5/7] tls: mark operator ConfigMap targets as managementClusterComponent Add managementClusterComponent: true to ConfigMap targets for controller-manager, kube-apiserver, openshift-apiserver, kube-controller-manager, and kube-scheduler to match the corresponding observedConfigTargets and serviceTargets. Also update g.Skip messages to use "management-cluster component" wording consistently. --- test/extended/tls/tls_observed_config.go | 26 ++++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/test/extended/tls/tls_observed_config.go b/test/extended/tls/tls_observed_config.go index 34e1f6b1d14f..e2be542d6753 100644 --- a/test/extended/tls/tls_observed_config.go +++ b/test/extended/tls/tls_observed_config.go @@ -109,12 +109,12 @@ var observedConfigTargets = []observedConfigTarget{ var configMapTargets = []configMapTarget{ {namespace: "openshift-image-registry", configMapName: "image-registry-operator-config", configMapNamespace: "openshift-image-registry", configMapKey: "config.yaml"}, - {namespace: "openshift-controller-manager", configMapName: "openshift-controller-manager-operator-config", configMapNamespace: "openshift-controller-manager-operator", configMapKey: "config.yaml"}, - {namespace: "openshift-kube-apiserver", configMapName: "kube-apiserver-operator-config", configMapNamespace: "openshift-kube-apiserver-operator", configMapKey: "config.yaml"}, - {namespace: "openshift-apiserver", configMapName: "openshift-apiserver-operator-config", configMapNamespace: "openshift-apiserver-operator", configMapKey: "config.yaml"}, + {namespace: "openshift-controller-manager", configMapName: "openshift-controller-manager-operator-config", configMapNamespace: "openshift-controller-manager-operator", configMapKey: "config.yaml", managementClusterComponent: true}, + {namespace: "openshift-kube-apiserver", configMapName: "kube-apiserver-operator-config", configMapNamespace: "openshift-kube-apiserver-operator", configMapKey: "config.yaml", managementClusterComponent: true}, + {namespace: "openshift-apiserver", configMapName: "openshift-apiserver-operator-config", configMapNamespace: "openshift-apiserver-operator", configMapKey: "config.yaml", managementClusterComponent: true}, {namespace: "openshift-etcd", configMapName: "etcd-operator-config", configMapNamespace: "openshift-etcd-operator", configMapKey: "config.yaml", managementClusterComponent: true}, - {namespace: "openshift-kube-controller-manager", configMapName: "kube-controller-manager-operator-config", configMapNamespace: "openshift-kube-controller-manager-operator", configMapKey: "config.yaml"}, - {namespace: "openshift-kube-scheduler", configMapName: "openshift-kube-scheduler-operator-config", configMapNamespace: "openshift-kube-scheduler-operator", configMapKey: "config.yaml"}, + {namespace: "openshift-kube-controller-manager", configMapName: "kube-controller-manager-operator-config", configMapNamespace: "openshift-kube-controller-manager-operator", configMapKey: "config.yaml", managementClusterComponent: true}, + {namespace: "openshift-kube-scheduler", configMapName: "openshift-kube-scheduler-operator-config", configMapNamespace: "openshift-kube-scheduler-operator", configMapKey: "config.yaml", managementClusterComponent: true}, {namespace: "openshift-cluster-samples-operator", configMapName: "samples-operator-config", configMapNamespace: "openshift-cluster-samples-operator", configMapKey: "config.yaml"}, } @@ -243,7 +243,7 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Suite target := target g.It(fmt.Sprintf("should populate ObservedConfig with TLS settings - %s", target.namespace), func() { if isHyperShiftCluster && target.managementClusterComponent { - g.Skip(fmt.Sprintf("Skipping control-plane target %s on HyperShift (runs on management cluster)", target.namespace)) + g.Skip(fmt.Sprintf("Skipping management-cluster component %s on HyperShift", target.namespace)) } testObservedConfig(oc, ctx, target) }) @@ -254,7 +254,7 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Suite target := target g.It(fmt.Sprintf("should have TLS config injected into ConfigMap - %s", target.namespace), func() { if isHyperShiftCluster && target.managementClusterComponent { - g.Skip(fmt.Sprintf("Skipping control-plane target %s on HyperShift (runs on management cluster)", target.namespace)) + g.Skip(fmt.Sprintf("Skipping management-cluster component %s on HyperShift", target.namespace)) } testConfigMapTLSInjection(oc, ctx, target) }) @@ -265,7 +265,7 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Suite target := target g.It(fmt.Sprintf("should propagate TLS config to deployment env vars - %s", target.namespace), func() { if isHyperShiftCluster && target.managementClusterComponent { - g.Skip(fmt.Sprintf("Skipping control-plane target %s on HyperShift (runs on management cluster)", target.namespace)) + g.Skip(fmt.Sprintf("Skipping management-cluster component %s on HyperShift", target.namespace)) } testDeploymentTLSEnvVars(oc, ctx, target) }) @@ -276,7 +276,7 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Suite target := target g.It(fmt.Sprintf("should enforce TLS version at the wire level - %s:%s", target.namespace, target.servicePort), func() { if isHyperShiftCluster && target.managementClusterComponent { - g.Skip(fmt.Sprintf("Skipping control-plane target %s:%s on HyperShift (runs on management cluster)", target.namespace, target.servicePort)) + g.Skip(fmt.Sprintf("Skipping management-cluster component %s:%s on HyperShift", target.namespace, target.servicePort)) } testWireLevelTLS(oc, ctx, target) }) @@ -340,28 +340,28 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Disru g.It(fmt.Sprintf("should restore inject-tls annotation after deletion - %s", target.namespace), func() { if isHyperShiftCluster && target.managementClusterComponent { - g.Skip(fmt.Sprintf("Skipping control-plane target %s on HyperShift (runs on management cluster)", target.namespace)) + g.Skip(fmt.Sprintf("Skipping management-cluster component %s on HyperShift", target.namespace)) } testAnnotationRestorationAfterDeletion(oc, ctx, target) }) g.It(fmt.Sprintf("should restore inject-tls annotation when set to false - %s", target.namespace), func() { if isHyperShiftCluster && target.managementClusterComponent { - g.Skip(fmt.Sprintf("Skipping control-plane target %s on HyperShift (runs on management cluster)", target.namespace)) + g.Skip(fmt.Sprintf("Skipping management-cluster component %s on HyperShift", target.namespace)) } testAnnotationRestorationWhenFalse(oc, ctx, target) }) g.It(fmt.Sprintf("should restore servingInfo after removal - %s", target.namespace), func() { if isHyperShiftCluster && target.managementClusterComponent { - g.Skip(fmt.Sprintf("Skipping control-plane target %s on HyperShift (runs on management cluster)", target.namespace)) + g.Skip(fmt.Sprintf("Skipping management-cluster component %s on HyperShift", target.namespace)) } testServingInfoRestorationAfterRemoval(oc, ctx, target) }) g.It(fmt.Sprintf("should restore servingInfo after modification - %s", target.namespace), func() { if isHyperShiftCluster && target.managementClusterComponent { - g.Skip(fmt.Sprintf("Skipping control-plane target %s on HyperShift (runs on management cluster)", target.namespace)) + g.Skip(fmt.Sprintf("Skipping management-cluster component %s on HyperShift", target.namespace)) } testServingInfoRestorationAfterModification(oc, ctx, target) }) From d48665db72c624239cc260112e49c52b439d55b3 Mon Sep 17 00:00:00 2001 From: gangwgr Date: Wed, 20 May 2026 17:37:33 +0530 Subject: [PATCH 6/7] tls: convert clusterOperatorNames to struct-based targets Replace the plain string slice clusterOperatorNames with a typed clusterOperatorTarget struct carrying managementClusterComponent, consistent with all other target types. Add guestSideClusterOperatorTargets() filter function matching the pattern used by guestSideObservedConfigTargets() et al. Also add a comment explaining why the samples operator is absent from observedConfigTargets (it uses samples.operator.openshift.io/v1 Config which has no spec.observedConfig). --- test/extended/tls/tls_observed_config.go | 91 ++++++++++++++---------- 1 file changed, 52 insertions(+), 39 deletions(-) diff --git a/test/extended/tls/tls_observed_config.go b/test/extended/tls/tls_observed_config.go index e2be542d6753..4dbba8658844 100644 --- a/test/extended/tls/tls_observed_config.go +++ b/test/extended/tls/tls_observed_config.go @@ -50,46 +50,46 @@ const ( // observedConfigTarget identifies an operator whose spec.observedConfig // must contain servingInfo with minTLSVersion and cipherSuites. type observedConfigTarget struct { - namespace string - operatorConfigGVR schema.GroupVersionResource - operatorConfigName string + namespace string + operatorConfigGVR schema.GroupVersionResource + operatorConfigName string managementClusterComponent bool } // configMapTarget identifies a ConfigMap that CVO injects TLS config into. type configMapTarget struct { - namespace string // workload namespace (used in test names) - configMapName string - configMapNamespace string // namespace where the ConfigMap lives - configMapKey string // data key within the ConfigMap + namespace string // workload namespace (used in test names) + configMapName string + configMapNamespace string // namespace where the ConfigMap lives + configMapKey string // data key within the ConfigMap managementClusterComponent bool } // deploymentEnvVarTarget identifies a Deployment whose containers must // have TLS-related environment variables matching the cluster profile. type deploymentEnvVarTarget struct { - namespace string - deploymentName string - tlsMinVersionEnvVar string - cipherSuitesEnvVar string + namespace string + deploymentName string + tlsMinVersionEnvVar string + cipherSuitesEnvVar string managementClusterComponent bool } // serviceTarget identifies a Service endpoint that must enforce the // cluster TLS profile at the wire level. type serviceTarget struct { - namespace string - serviceName string - servicePort string - deploymentName string // for waiting on rollout before probing + namespace string + serviceName string + servicePort string + deploymentName string // for waiting on rollout before probing managementClusterComponent bool } // deploymentRolloutTarget identifies a Deployment that must complete // rollout after a TLS profile change. type deploymentRolloutTarget struct { - namespace string - deploymentName string + namespace string + deploymentName string managementClusterComponent bool } @@ -97,6 +97,11 @@ type deploymentRolloutTarget struct { // Each list contains exactly the entries relevant to one test category. // Entries are derived from `targets` but only carry the fields the test uses. +// observedConfigTargets lists operator configs that populate +// spec.observedConfig.servingInfo with TLS settings via library-go. +// The samples operator is NOT included because it uses +// samples.operator.openshift.io/v1 Config (no spec.observedConfig); +// its TLS config is injected through the ConfigMap annotation instead. var observedConfigTargets = []observedConfigTarget{ {namespace: "openshift-image-registry", operatorConfigGVR: schema.GroupVersionResource{Group: "imageregistry.operator.openshift.io", Version: "v1", Resource: "configs"}, operatorConfigName: "cluster"}, {namespace: "openshift-controller-manager", operatorConfigGVR: schema.GroupVersionResource{Group: "operator.openshift.io", Version: "v1", Resource: "openshiftcontrollermanagers"}, operatorConfigName: "cluster", managementClusterComponent: true}, @@ -136,24 +141,22 @@ var serviceTargets = []serviceTarget{ {namespace: "openshift-cluster-samples-operator", serviceName: "metrics", servicePort: "60000", deploymentName: "cluster-samples-operator"}, } -// clusterOperatorNames is the deduplicated list of ClusterOperator names. -var clusterOperatorNames = []string{ - "image-registry", - "openshift-controller-manager", - "kube-apiserver", - "openshift-apiserver", - "etcd", - "kube-controller-manager", - "kube-scheduler", - "openshift-samples", +// clusterOperatorTarget identifies a ClusterOperator whose stability is +// verified after a TLS profile change. +type clusterOperatorTarget struct { + name string + managementClusterComponent bool } -// guestSideClusterOperatorNames lists ClusterOperators that exist on the -// guest cluster. On HyperShift, management-cluster components (etcd, -// kube-apiserver, etc.) have no CO on the guest side and must be skipped. -var guestSideClusterOperatorNames = []string{ - "image-registry", - "openshift-samples", +var clusterOperatorTargets = []clusterOperatorTarget{ + {name: "image-registry"}, + {name: "openshift-controller-manager", managementClusterComponent: true}, + {name: "kube-apiserver", managementClusterComponent: true}, + {name: "openshift-apiserver", managementClusterComponent: true}, + {name: "etcd", managementClusterComponent: true}, + {name: "kube-controller-manager", managementClusterComponent: true}, + {name: "kube-scheduler", managementClusterComponent: true}, + {name: "openshift-samples"}, } var deploymentRolloutTargets = []deploymentRolloutTarget{ @@ -216,6 +219,16 @@ func guestSideDeploymentRolloutTargets() []deploymentRolloutTarget { return result } +func guestSideClusterOperatorTargets() []clusterOperatorTarget { + var result []clusterOperatorTarget + for _, t := range clusterOperatorTargets { + if !t.managementClusterComponent { + result = append(result, t) + } + } + return result +} + // ── read-only tests ──────────────────────────────────────────── // These tests only read cluster state (ObservedConfig, ConfigMaps, var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Suite:openshift/tls-observed-config]", func() { @@ -1676,9 +1689,9 @@ func waitForAllOperatorsAfterTLSChange(oc *exutil.CLI, ctx context.Context, prof time.Sleep(30 * time.Second) e2e.Logf("Waiting for all ClusterOperators to stabilize after %s profile change", profileLabel) - for _, co := range clusterOperatorNames { - e2e.Logf("Waiting for ClusterOperator %s to stabilize after %s switch", co, profileLabel) - waitForClusterOperatorStable(oc, ctx, co) + for _, co := range clusterOperatorTargets { + e2e.Logf("Waiting for ClusterOperator %s to stabilize after %s switch", co.name, profileLabel) + waitForClusterOperatorStable(oc, ctx, co.name) } for _, t := range deploymentRolloutTargets { @@ -1793,9 +1806,9 @@ func waitForHCPAppReady(mgmtCLI *exutil.CLI, appLabel, hcpNS string, timeout tim // and Deployments to stabilize after a TLS profile change on HyperShift. func waitForGuestOperatorsAfterTLSChange(oc *exutil.CLI, ctx context.Context, profileLabel string, rollouts []deploymentRolloutTarget) { e2e.Logf("Waiting for guest-side ClusterOperators to stabilize after %s profile change", profileLabel) - for _, co := range guestSideClusterOperatorNames { - e2e.Logf("Waiting for ClusterOperator %s to stabilize after %s switch", co, profileLabel) - waitForClusterOperatorStable(oc, ctx, co) + for _, co := range guestSideClusterOperatorTargets() { + e2e.Logf("Waiting for ClusterOperator %s to stabilize after %s switch", co.name, profileLabel) + waitForClusterOperatorStable(oc, ctx, co.name) } for _, t := range rollouts { From 9485c95f014bdccc0dc1c790bcb3f33b72173f2e Mon Sep 17 00:00:00 2001 From: gangwgr Date: Wed, 20 May 2026 20:35:08 +0530 Subject: [PATCH 7/7] tls: wait for deployment pod rollout before wire-level TLS checks After a TLS profile change on HyperShift, the ConfigMap injection updates quickly but the backing deployment may not have restarted its pods yet. Add waitForDeploymentRolloutAfterTLSChange which captures existing pod UIDs, then polls until old pods are gone and the deployment is fully ready. This ensures the running pods serve with the new TLS config before wire-level checks are performed. Fixes the "TLS 1.2 should be REJECTED but succeeded" failure for svc/metrics in openshift-cluster-samples-operator. --- test/extended/tls/tls_observed_config.go | 76 ++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/test/extended/tls/tls_observed_config.go b/test/extended/tls/tls_observed_config.go index 4dbba8658844..25229df80b57 100644 --- a/test/extended/tls/tls_observed_config.go +++ b/test/extended/tls/tls_observed_config.go @@ -449,6 +449,9 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Disru tlsShouldNotWork := &tls.Config{MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12, InsecureSkipVerify: true} for _, t := range guestSvcs { g.By(fmt.Sprintf("wire-level TLS check: svc/%s in %s (expecting Modern = TLS 1.3 only)", t.serviceName, t.namespace)) + if t.deploymentName != "" { + waitForDeploymentRolloutAfterTLSChange(oc, configChangeCtx, t.namespace, t.deploymentName) + } err = forwardPortAndExecute(t.serviceName, t.namespace, t.servicePort, func(localPort int) error { return checkTLSConnection(localPort, tlsShouldWork, tlsShouldNotWork, t) }) o.Expect(err).NotTo(o.HaveOccurred()) @@ -635,6 +638,9 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Disru g.By("verifying wire-level TLS for Custom profile (TLS 1.2) on guest targets") for _, t := range guestSvcs { + if t.deploymentName != "" { + waitForDeploymentRolloutAfterTLSChange(oc, configChangeCtx, t.namespace, t.deploymentName) + } shouldWork := &tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS12} shouldNotWork := &tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS10, MaxVersion: tls.VersionTLS11} err := forwardPortAndExecute(t.serviceName, t.namespace, t.servicePort, func(localPort int) error { @@ -1587,6 +1593,76 @@ func checkTLSConnection(localPort int, shouldWork, shouldNotWork *tls.Config, t return nil } +// waitForDeploymentRolloutAfterTLSChange waits for a deployment's pods to be +// replaced after a TLS config change. It captures the current pod UIDs, then +// polls until all old pods are gone and the deployment is fully ready. This +// ensures the running pods have picked up the new TLS configuration before +// wire-level checks are performed. +func waitForDeploymentRolloutAfterTLSChange(oc *exutil.CLI, ctx context.Context, namespace, deploymentName string) { + e2e.Logf("Waiting for deployment %s/%s to roll out new pods after TLS change", namespace, deploymentName) + + oldPods := make(map[string]bool) + podList, err := oc.AdminKubeClient().CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{}) + if err == nil { + for _, p := range podList.Items { + if strings.Contains(p.Name, deploymentName) { + oldPods[string(p.UID)] = true + } + } + } + e2e.Logf("Captured %d existing pods for deployment %s/%s", len(oldPods), namespace, deploymentName) + + err = wait.PollUntilContextTimeout(ctx, 10*time.Second, 5*time.Minute, true, + func(ctx context.Context) (bool, error) { + deployment, err := oc.AdminKubeClient().AppsV1().Deployments(namespace).Get(ctx, deploymentName, metav1.GetOptions{}) + if err != nil { + return false, nil + } + + replicas := int32(1) + if deployment.Spec.Replicas != nil { + replicas = *deployment.Spec.Replicas + } + + if deployment.Status.UpdatedReplicas < replicas || + deployment.Status.ReadyReplicas < replicas || + deployment.Status.UnavailableReplicas > 0 { + e2e.Logf(" poll: deployment %s/%s rolling (updated=%d, ready=%d, unavailable=%d)", + namespace, deploymentName, + deployment.Status.UpdatedReplicas, + deployment.Status.ReadyReplicas, + deployment.Status.UnavailableReplicas) + return false, nil + } + + if deployment.Status.ObservedGeneration < deployment.Generation { + e2e.Logf(" poll: deployment %s/%s generation not yet observed (%d < %d)", + namespace, deploymentName, + deployment.Status.ObservedGeneration, deployment.Generation) + return false, nil + } + + currentPods, err := oc.AdminKubeClient().CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return false, nil + } + for _, p := range currentPods.Items { + if oldPods[string(p.UID)] && p.DeletionTimestamp == nil { + if strings.Contains(p.Name, deploymentName) { + e2e.Logf(" poll: old pod %s still running in %s", p.Name, namespace) + return false, nil + } + } + } + + e2e.Logf("Deployment %s/%s has rolled out new pods", namespace, deploymentName) + return true, nil + }) + if err != nil { + e2e.Logf("WARNING: deployment %s/%s rollout wait timed out: %v (proceeding with wire-level check)", namespace, deploymentName, err) + } +} + // waitForDeploymentCompleteWithTimeout waits for a deployment to complete rollout // with a configurable timeout. This is a wrapper around the standard k8s e2e // deployment helper but with an extended timeout for slow rollouts.