From 162cde1f00a70baf385d7e1f3cb4af95c55cc4cd Mon Sep 17 00:00:00 2001 From: Jakub Hadvig Date: Wed, 24 Jun 2026 21:52:02 +0200 Subject: [PATCH] pkg/readiness: Handle missing etcd ClusterOperator on HyperShift On HyperShift hosted clusters, etcd runs on the management cluster and the etcd ClusterOperator does not exist on the guest cluster. The EtcdHealthCheck now handles the NotFound case by returning externally_managed: true instead of erroring, which prevents the readiness check from failing on HyperShift. The etcd e2e comparison test is also skipped on HyperShift since the openshift-etcd namespace does not exist on hosted clusters, making ground-truth pod comparison impossible. Co-Authored-By: Claude Opus 4.6 (1M context) --- pkg/readiness/etcd_health.go | 11 ++++++++++- test/cvo/readiness.go | 7 ++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/pkg/readiness/etcd_health.go b/pkg/readiness/etcd_health.go index 6ddc9547b..4d3da67fb 100644 --- a/pkg/readiness/etcd_health.go +++ b/pkg/readiness/etcd_health.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/client-go/dynamic" ) @@ -16,9 +17,17 @@ func (c *EtcdHealthCheck) Run(ctx context.Context, dc dynamic.Interface, current result := map[string]any{} var sectionErrors []map[string]any - // Check etcd ClusterOperator + // Check etcd ClusterOperator — on HyperShift hosted clusters etcd is + // managed externally and the CO does not exist on the guest cluster. etcdCO, err := GetResource(ctx, dc, GVRClusterOperator, "etcd") if err != nil { + if kerrors.IsNotFound(err) { + return map[string]any{ + "externally_managed": true, + "total_members": 0, + "healthy_members": 0, + }, nil + } return nil, fmt.Errorf("failed to get etcd ClusterOperator: %w", err) } diff --git a/test/cvo/readiness.go b/test/cvo/readiness.go index 106dcb0c6..96652b54f 100644 --- a/test/cvo/readiness.go +++ b/test/cvo/readiness.go @@ -11,6 +11,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1" @@ -24,6 +25,7 @@ var _ = g.Describe(`[Jira:"Cluster Version Operator"] cluster-version-operator r kubeClient kubernetes.Interface configClient *configv1client.ConfigV1Client ctx context.Context + restCfg *rest.Config currentVersion string targetVersion string ) @@ -33,7 +35,8 @@ var _ = g.Describe(`[Jira:"Cluster Version Operator"] cluster-version-operator r ctx, cancel = context.WithTimeout(context.Background(), 2*time.Minute) g.DeferCleanup(cancel) - restCfg, err := util.GetRestConfig() + var err error + restCfg, err = util.GetRestConfig() o.Expect(err).NotTo(o.HaveOccurred()) dynamicClient, err = dynamic.NewForConfig(restCfg) @@ -138,6 +141,8 @@ var _ = g.Describe(`[Jira:"Cluster Version Operator"] cluster-version-operator r }) g.It("should report etcd member count matching actual etcd pods", func() { + o.Expect(util.SkipIfHypershift(ctx, restCfg)).To(o.BeNil()) + // Ground truth: list etcd pods via typed client podList, err := kubeClient.CoreV1().Pods("openshift-etcd").List(ctx, metav1.ListOptions{ LabelSelector: "app=etcd",