diff --git a/api/hypershift/v1beta1/hostedcluster_conditions.go b/api/hypershift/v1beta1/hostedcluster_conditions.go index 29f473fbd22..6f446bb6a0d 100644 --- a/api/hypershift/v1beta1/hostedcluster_conditions.go +++ b/api/hypershift/v1beta1/hostedcluster_conditions.go @@ -195,6 +195,13 @@ const ( // This condition is used to track the status of the recovery process and to determine if the HostedCluster // is ready to be used after restoration. HostedClusterRestoredFromBackup ConditionType = "HostedClusterRestoredFromBackup" + + // ControlPlaneToDataPlaneConnectivityHealthy indicates whether the control plane can successfully + // reach the data plane components. + // When True, control plane has healthy connectivity to data plane nodes. + // When False, there are network connectivity issues preventing control plane from reaching the data plane. + // A failure here may indicate network policy issues, firewall rules, or infrastructure problems. + ControlPlaneToDataPlaneConnectivityHealthy ConditionType = "ControlPlaneToDataPlaneConnectivityHealthy" ) // Reasons. @@ -251,6 +258,12 @@ const ( RecoveryFinishedReason = "RecoveryFinished" CloudResourcesCleanupSkippedReason = "CloudResourcesCleanupSkipped" + + ControlPlaneToDataPlaneReasonNoKonnectivityAgentPodsFound = "NoKonnectivityAgentPodsFound" + + ControlPlaneToDataPlaneReasonLogAccessFailed = "ControlPlaneToDataPlaneLogAccessFailed" + + ControlPlaneToDataPlaneReasonNoWorkerNodesAvailable = "NoWorkerNodesAvailable" ) // Messages. diff --git a/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go b/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go index 1f71754e946..2b04cfb0cc3 100644 --- a/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go +++ b/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go @@ -71,6 +71,7 @@ import ( "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" + clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" "k8s.io/utils/ptr" @@ -127,6 +128,7 @@ exec /bin/azure-cloud-node-manager \ type reconciler struct { client client.Client uncachedClient client.Client + clientSet *clientset.Clientset upsert.CreateOrUpdateProvider platformType hyperv1.PlatformType rootCA string @@ -144,6 +146,18 @@ type reconciler struct { operateOnReleaseImage string ImageMetaDataProvider util.ImageMetadataProvider cleanupTracker *util.CleanupTracker + + // exposed for unit test since GetLogs looks hard to be mocked + GetPodLogs func(context context.Context, clientset *clientset.Clientset, namespace, name, container string) ([]byte, error) +} + +func getPodLogs(ctx context.Context, clientSet *clientset.Clientset, namespace, name, container string) ([]byte, error) { + limit := int64(1024) + opts := &corev1.PodLogOptions{ + Container: container, + LimitBytes: &limit, + } + return clientSet.CoreV1().Pods(namespace).GetLogs(name, opts).DoRaw(ctx) } // eventHandler is the handler used throughout. As this controller reconciles all kind of different resources @@ -181,9 +195,15 @@ func Setup(ctx context.Context, opts *operator.HostedClusterConfigOperatorConfig return fmt.Errorf("failed to create kubevirt infra uncached client: %w", err) } + clientset, err := clientset.NewForConfig(opts.Manager.GetConfig()) + if err != nil { + return fmt.Errorf("failed to initialize kubeClient from config: %w", err) + } + c, err := controller.New(ControllerName, opts.Manager, controller.Options{Reconciler: &reconciler{ client: opts.Manager.GetClient(), uncachedClient: uncachedClient, + clientSet: clientset, CreateOrUpdateProvider: opts.TargetCreateOrUpdateProvider, platformType: opts.PlatformType, rootCA: opts.InitialCA, @@ -201,6 +221,7 @@ func Setup(ctx context.Context, opts *operator.HostedClusterConfigOperatorConfig operateOnReleaseImage: opts.OperateOnReleaseImage, ImageMetaDataProvider: opts.ImageMetaDataProvider, cleanupTracker: util.NewCleanupTracker(), + GetPodLogs: getPodLogs, }}) if err != nil { return fmt.Errorf("failed to construct controller: %w", err) @@ -539,6 +560,11 @@ func (r *reconciler) Reconcile(ctx context.Context, _ ctrl.Request) (ctrl.Result errs = append(errs, fmt.Errorf("failed to reconcile konnectivity agent: %w", err)) } + log.Info("reconciling control-Plane to data-Plane status conditions") + if err := r.reconcileControlPlaneDataPlaneConnectivityConditions(ctx, hcp, log); err != nil { + errs = append(errs, fmt.Errorf("failed to update ControlPlaneToDataPlaneConnectivity condition: %w", err)) + } + log.Info("reconciling openshift apiserver apiservices") if err := r.reconcileOpenshiftAPIServerAPIServices(ctx, hcp); err != nil { errs = append(errs, fmt.Errorf("failed to reconcile openshift apiserver service: %w", err)) @@ -1389,6 +1415,79 @@ func (r *reconciler) reconcileClusterVersion(ctx context.Context, hcp *hyperv1.H return nil } +func (r *reconciler) reconcileControlPlaneDataPlaneConnectivityConditions(ctx context.Context, hcp *hyperv1.HostedControlPlane, log logr.Logger) error { + patchHCPWithCondition := func(hcp *hyperv1.HostedControlPlane, condition *metav1.Condition) error { + originalHCP := hcp.DeepCopy() + if !meta.SetStatusCondition(&hcp.Status.Conditions, *condition) { + return nil // No status change; avoid unnecessary API call. + } + if err := r.cpClient.Status().Patch(ctx, hcp, client.MergeFrom(originalHCP)); err != nil { + return fmt.Errorf("failed to update HostedControlPlane status with %s condition: %w", condition.Type, err) + } + log.Info(string(hyperv1.ControlPlaneToDataPlaneConnectivityHealthy) + " updated") + return nil + } + + condition := &metav1.Condition{ + Type: string(hyperv1.ControlPlaneToDataPlaneConnectivityHealthy), + Status: metav1.ConditionFalse, // False by default + } + totalNodes, err := util.CountAvailableNodes(ctx, r.client) + if err != nil { + condition.Status = metav1.ConditionUnknown + condition.Reason = hyperv1.ControlPlaneToDataPlaneReasonNoWorkerNodesAvailable + condition.Message = "Unable to count worker nodes: " + err.Error() + return patchHCPWithCondition(hcp, condition) + } + if totalNodes == 0 { + condition.Status = metav1.ConditionUnknown + condition.Reason = hyperv1.ControlPlaneToDataPlaneReasonNoWorkerNodesAvailable + condition.Message = "No worker nodes available" + return patchHCPWithCondition(hcp, condition) + } + var podList corev1.PodList + if err := r.uncachedClient.List(ctx, &podList, + client.MatchingLabels{"app": "konnectivity-agent"}, client.InNamespace("kube-system")); err != nil { + condition.Reason = hyperv1.ControlPlaneToDataPlaneReasonNoKonnectivityAgentPodsFound + condition.Message = "Couldn't list konnectivity-agent PODs in kube-system namespace: " + err.Error() + return patchHCPWithCondition(hcp, condition) + } + + logsFound := false + runningPodsFound := false + for _, pod := range podList.Items { + if pod.Status.Phase != corev1.PodRunning { + continue + } + runningPodsFound = true + data, err := r.GetPodLogs(ctx, r.clientSet, pod.Namespace, pod.Name, "konnectivity-agent") + if err != nil { + log.Error(err, + fmt.Sprintf("failed to get logs for konnectivity-agent pod %s/%s", pod.Namespace, pod.Name)) + continue + } + if len(data) > 0 { + logsFound = true + break + } + } + if !runningPodsFound { + condition.Reason = hyperv1.ControlPlaneToDataPlaneReasonNoKonnectivityAgentPodsFound + condition.Message = "Couldn't find any konnectivity-agent running in data plane" + } else { + if !logsFound { + condition.Reason = hyperv1.ControlPlaneToDataPlaneReasonNoKonnectivityAgentPodsFound + condition.Message = "failed to read konnectivity-agent logs from data plane" + } else { + condition.Status = metav1.ConditionTrue + condition.Reason = hyperv1.AsExpectedReason + condition.Message = hyperv1.AllIsWellMessage + } + } + + return patchHCPWithCondition(hcp, condition) +} + func (r *reconciler) reconcileOpenshiftAPIServerAPIServices(ctx context.Context, hcp *hyperv1.HostedControlPlane) error { rootCA := cpomanifests.RootCASecret(hcp.Namespace) if err := r.cpClient.Get(ctx, client.ObjectKeyFromObject(rootCA), rootCA); err != nil { diff --git a/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go b/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go index f5133389ba8..2605a3e6859 100644 --- a/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go +++ b/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go @@ -38,6 +38,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation" + clientset "k8s.io/client-go/kubernetes" "k8s.io/utils/ptr" controllerruntime "sigs.k8s.io/controller-runtime" @@ -163,7 +164,7 @@ func TestReconcileErrorHandling(t *testing.T) { CreateOrUpdateProvider: &simpleCreateOrUpdater{}, platformType: hyperv1.NonePlatform, clusterSignerCA: "foobar", - cpClient: fake.NewClientBuilder().WithScheme(api.Scheme).WithObjects(cpObjects...).Build(), + cpClient: fake.NewClientBuilder().WithScheme(api.Scheme).WithObjects(cpObjects...).WithStatusSubresource(&hyperv1.HostedControlPlane{}).Build(), hcpName: "foo", hcpNamespace: "bar", releaseProvider: &fakereleaseprovider.FakeReleaseProvider{}, @@ -2349,3 +2350,207 @@ func TestReconcileAuthOIDC(t *testing.T) { }) } } + +func newCondition(conditionType string, status metav1.ConditionStatus, reason, message string) *metav1.Condition { + return &metav1.Condition{ + Type: conditionType, + Reason: reason, + Status: status, + Message: message, + } +} + +func Test_reconciler_reconcileControlPlaneDataPlaneConnectivityConditions(t *testing.T) { + newKonnectivityAgentPod := func(name string, phase corev1.PodPhase) corev1.Pod { + return corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "kube-system", + Labels: map[string]string{"app": "konnectivity-agent"}, + }, + Status: corev1.PodStatus{ + Phase: phase, + }, + } + } + newRunningNode := func(name string) corev1.Node { + return corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: corev1.NodeSpec{ + Unschedulable: false, + }, + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + {Type: corev1.NodeReady, + Status: corev1.ConditionTrue, + }, + }, + }, + } + } + + tests := []struct { + name string + hcp *hyperv1.HostedControlPlane + wantErr bool + expectedCondition *metav1.Condition + pods []corev1.Pod + nodes []corev1.Node + mockedGetPodLogs func(context context.Context, clientet *clientset.Clientset, namespace, name, container string) ([]byte, error) + }{ + { + name: "no worker nodes Condition Unknown", + hcp: fakeHCP(), + wantErr: false, + expectedCondition: newCondition(string(hyperv1.ControlPlaneToDataPlaneConnectivityHealthy), + metav1.ConditionUnknown, hyperv1.ControlPlaneToDataPlaneReasonNoWorkerNodesAvailable, "No worker nodes available"), + nodes: []corev1.Node{}, + mockedGetPodLogs: func(context context.Context, + clientet *clientset.Clientset, + namespace, name, + container string) ([]byte, error) { + return nil, nil + }, + }, + { + name: "no konnectivity-agent PODs condition False", + hcp: fakeHCP(), + wantErr: false, + expectedCondition: newCondition(string(hyperv1.ControlPlaneToDataPlaneConnectivityHealthy), + metav1.ConditionFalse, hyperv1.ControlPlaneToDataPlaneReasonNoKonnectivityAgentPodsFound, "Couldn't find any konnectivity-agent running in data plane"), + nodes: []corev1.Node{newRunningNode("node1")}, + pods: []corev1.Pod{}, + mockedGetPodLogs: func(context context.Context, + clientet *clientset.Clientset, + namespace, name, + container string) ([]byte, error) { + return nil, nil + }, + }, + { + name: "only one pending POD condition False", + hcp: fakeHCP(), + wantErr: false, + expectedCondition: newCondition(string(hyperv1.ControlPlaneToDataPlaneConnectivityHealthy), + metav1.ConditionFalse, hyperv1.ControlPlaneToDataPlaneReasonNoKonnectivityAgentPodsFound, "Couldn't find any konnectivity-agent running in data plane"), + nodes: []corev1.Node{newRunningNode("node1")}, + pods: []corev1.Pod{newKonnectivityAgentPod("konnectivity-agent-rdax", corev1.PodPending)}, + mockedGetPodLogs: func(context context.Context, + clientet *clientset.Clientset, + namespace, name, + container string) ([]byte, error) { + return nil, nil + }, + }, + { + name: "one konnectivity-agent PODs running condition OK", + hcp: fakeHCP(), + wantErr: false, + expectedCondition: newCondition(string(hyperv1.ControlPlaneToDataPlaneConnectivityHealthy), + metav1.ConditionTrue, hyperv1.AsExpectedReason, hyperv1.AllIsWellMessage), + nodes: []corev1.Node{newRunningNode("node1")}, + pods: []corev1.Pod{newKonnectivityAgentPod("konnectivity-agent-rdax", corev1.PodRunning)}, + mockedGetPodLogs: func(context context.Context, + clientet *clientset.Clientset, + namespace, name, + container string) ([]byte, error) { + return []byte("this is the log my friend"), nil + }, + }, + { + name: "may konnectivity-agent PODs only one running condition OK", + hcp: fakeHCP(), + wantErr: false, + expectedCondition: newCondition(string(hyperv1.ControlPlaneToDataPlaneConnectivityHealthy), + metav1.ConditionTrue, hyperv1.AsExpectedReason, hyperv1.AllIsWellMessage), + nodes: []corev1.Node{newRunningNode("node1")}, + pods: []corev1.Pod{newKonnectivityAgentPod("konnectivity-agent-rdax1", corev1.PodPending), + newKonnectivityAgentPod("konnectivity-agent-rdax2", corev1.PodPending), + newKonnectivityAgentPod("konnectivity-agent-rdax3", corev1.PodRunning), + newKonnectivityAgentPod("konnectivity-agent-rdax4", corev1.PodPending), + newKonnectivityAgentPod("konnectivity-agent-rdax5", corev1.PodPending)}, + mockedGetPodLogs: func(context context.Context, + clientet *clientset.Clientset, + namespace, name, + container string) ([]byte, error) { + return []byte("this is the log my friend"), nil + }, + }, + { + name: "one konnectivity-agent PODs running bad since error getting LOG", + hcp: fakeHCP(), + wantErr: false, + expectedCondition: newCondition(string(hyperv1.ControlPlaneToDataPlaneConnectivityHealthy), + metav1.ConditionFalse, hyperv1.ControlPlaneToDataPlaneReasonNoKonnectivityAgentPodsFound, + "failed to read konnectivity-agent logs from data plane"), + nodes: []corev1.Node{newRunningNode("node1")}, + pods: []corev1.Pod{newKonnectivityAgentPod("konnectivity-agent-rdax", corev1.PodRunning)}, + mockedGetPodLogs: func(context context.Context, + clientet *clientset.Clientset, + namespace, name, + container string) ([]byte, error) { + return nil, fmt.Errorf("this time we fail") + }, + }, + { + name: "one konnectivity-agent PODs running bad since no LOG", // unsure this is possible + hcp: fakeHCP(), + wantErr: false, + expectedCondition: newCondition(string(hyperv1.ControlPlaneToDataPlaneConnectivityHealthy), + metav1.ConditionFalse, hyperv1.ControlPlaneToDataPlaneReasonNoKonnectivityAgentPodsFound, + "failed to read konnectivity-agent logs from data plane"), + nodes: []corev1.Node{newRunningNode("node1")}, + pods: []corev1.Pod{newKonnectivityAgentPod("konnectivity-agent-rdax", corev1.PodRunning)}, + mockedGetPodLogs: func(context context.Context, + clientet *clientset.Clientset, + namespace, name, + container string) ([]byte, error) { + return nil, nil + }, + }, + } + log := zapr.NewLogger(zaptest.NewLogger(t)) + ctx := logr.NewContext(context.Background(), log) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var r reconciler + nodeList := &corev1.NodeList{ + Items: tt.nodes, + } + podList := &corev1.PodList{ + Items: tt.pods, + } + r.client = fake.NewClientBuilder().WithLists(nodeList).Build() + r.uncachedClient = fake.NewClientBuilder().WithLists(podList).Build() + r.cpClient = fake.NewClientBuilder().WithScheme(api.Scheme).WithObjects(tt.hcp).WithStatusSubresource(&hyperv1.HostedControlPlane{}).Build() + r.GetPodLogs = tt.mockedGetPodLogs + + gotErr := r.reconcileControlPlaneDataPlaneConnectivityConditions(ctx, tt.hcp, log) + if gotErr != nil { + if !tt.wantErr { + t.Errorf("reconcileControlPlaneDataPlaneConnectivityConditions() failed: %v", gotErr) + } + return + } + if tt.wantErr { + t.Fatal("reconcileControlPlaneDataPlaneConnectivityConditions() succeeded unexpectedly") + } + if tt.expectedCondition != nil { + found := false + for _, c := range tt.hcp.Status.Conditions { + if tt.expectedCondition.Type == c.Type && + tt.expectedCondition.Message == c.Message && + tt.expectedCondition.Status == c.Status && + tt.expectedCondition.Reason == c.Reason { + found = true + } + } + if !found { + t.Fatal("couldn't find expected condition") + } + } + }) + } +} diff --git a/docs/content/reference/api.md b/docs/content/reference/api.md index d8f4f2ac57f..2506e022365 100644 --- a/docs/content/reference/api.md +++ b/docs/content/reference/api.md @@ -4901,6 +4901,13 @@ underlying cluster’s ClusterVersion.
"RolloutComplete"
ControlPlaneComponentRolloutComplete indicates whether the ControlPlaneComponent has completed its rollout.
"ControlPlaneToDataPlaneConnectivityHealthy"
ControlPlaneToDataPlaneConnectivityHealthy indicates whether the control plane can successfully +reach the data plane components. +When True, control plane has healthy connectivity to data plane nodes. +When False, there are network connectivity issues preventing control plane from reaching the data plane. +A failure here may indicate network policy issues, firewall rules, or infrastructure problems.
+"EtcdAvailable"
EtcdAvailable bubbles up the same condition from HCP. It signals if etcd is available. A failure here often means a software bug or a non-stable cluster.
diff --git a/hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go b/hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go index cac5977f7a8..a043ece762b 100644 --- a/hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go +++ b/hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go @@ -825,6 +825,7 @@ func (r *HostedClusterReconciler) reconcile(ctx context.Context, req ctrl.Reques hyperv1.ValidReleaseInfo, hyperv1.ValidIDPConfiguration, hyperv1.HostedClusterRestoredFromBackup, + hyperv1.ControlPlaneToDataPlaneConnectivityHealthy, } for _, conditionType := range hcpConditions { diff --git a/support/conditions/conditions.go b/support/conditions/conditions.go index e0af8ffe9e7..e5f53bfa2bc 100644 --- a/support/conditions/conditions.go +++ b/support/conditions/conditions.go @@ -11,22 +11,23 @@ import ( func ExpectedHCConditions(hostedCluster *hyperv1.HostedCluster) map[hyperv1.ConditionType]metav1.ConditionStatus { conditions := map[hyperv1.ConditionType]metav1.ConditionStatus{ - hyperv1.HostedClusterAvailable: metav1.ConditionTrue, - hyperv1.InfrastructureReady: metav1.ConditionTrue, - hyperv1.KubeAPIServerAvailable: metav1.ConditionTrue, - hyperv1.IgnitionEndpointAvailable: metav1.ConditionTrue, - hyperv1.EtcdAvailable: metav1.ConditionTrue, - hyperv1.ValidReleaseInfo: metav1.ConditionTrue, - hyperv1.ValidHostedClusterConfiguration: metav1.ConditionTrue, - hyperv1.SupportedHostedCluster: metav1.ConditionTrue, - hyperv1.ClusterVersionSucceeding: metav1.ConditionTrue, - hyperv1.ClusterVersionAvailable: metav1.ConditionTrue, - hyperv1.ClusterVersionReleaseAccepted: metav1.ConditionTrue, - hyperv1.ReconciliationActive: metav1.ConditionTrue, - hyperv1.ReconciliationSucceeded: metav1.ConditionTrue, - hyperv1.ValidHostedControlPlaneConfiguration: metav1.ConditionTrue, - hyperv1.ValidReleaseImage: metav1.ConditionTrue, - hyperv1.PlatformCredentialsFound: metav1.ConditionTrue, + hyperv1.HostedClusterAvailable: metav1.ConditionTrue, + hyperv1.InfrastructureReady: metav1.ConditionTrue, + hyperv1.KubeAPIServerAvailable: metav1.ConditionTrue, + hyperv1.IgnitionEndpointAvailable: metav1.ConditionTrue, + hyperv1.EtcdAvailable: metav1.ConditionTrue, + hyperv1.ValidReleaseInfo: metav1.ConditionTrue, + hyperv1.ValidHostedClusterConfiguration: metav1.ConditionTrue, + hyperv1.SupportedHostedCluster: metav1.ConditionTrue, + hyperv1.ClusterVersionSucceeding: metav1.ConditionTrue, + hyperv1.ClusterVersionAvailable: metav1.ConditionTrue, + hyperv1.ClusterVersionReleaseAccepted: metav1.ConditionTrue, + hyperv1.ReconciliationActive: metav1.ConditionTrue, + hyperv1.ReconciliationSucceeded: metav1.ConditionTrue, + hyperv1.ValidHostedControlPlaneConfiguration: metav1.ConditionTrue, + hyperv1.ValidReleaseImage: metav1.ConditionTrue, + hyperv1.PlatformCredentialsFound: metav1.ConditionTrue, + hyperv1.ControlPlaneToDataPlaneConnectivityHealthy: metav1.ConditionTrue, hyperv1.HostedClusterProgressing: metav1.ConditionFalse, hyperv1.HostedClusterDegraded: metav1.ConditionFalse, diff --git a/test/e2e/util/util.go b/test/e2e/util/util.go index 6446371f447..6ff6afe1132 100644 --- a/test/e2e/util/util.go +++ b/test/e2e/util/util.go @@ -2900,12 +2900,16 @@ func ValidateHostedClusterConditions(t *testing.T, ctx context.Context, client c expectedConditions[hyperv1.ClusterVersionAvailable] = metav1.ConditionFalse expectedConditions[hyperv1.ClusterVersionSucceeding] = metav1.ConditionFalse expectedConditions[hyperv1.ClusterVersionProgressing] = metav1.ConditionTrue + expectedConditions[hyperv1.ControlPlaneToDataPlaneConnectivityHealthy] = metav1.ConditionUnknown delete(expectedConditions, hyperv1.ValidKubeVirtInfraNetworkMTU) } if IsLessThan(Version415) { // ValidKubeVirtInfraNetworkMTU condition is not present in versions < 4.15 delete(expectedConditions, hyperv1.ValidKubeVirtInfraNetworkMTU) } + if IsLessThan(Version421) { + delete(expectedConditions, hyperv1.ControlPlaneToDataPlaneConnectivityHealthy) + } var predicates []Predicate[*hyperv1.HostedCluster] for conditionType, conditionStatus := range expectedConditions { diff --git a/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_conditions.go b/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_conditions.go index 29f473fbd22..6f446bb6a0d 100644 --- a/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_conditions.go +++ b/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_conditions.go @@ -195,6 +195,13 @@ const ( // This condition is used to track the status of the recovery process and to determine if the HostedCluster // is ready to be used after restoration. HostedClusterRestoredFromBackup ConditionType = "HostedClusterRestoredFromBackup" + + // ControlPlaneToDataPlaneConnectivityHealthy indicates whether the control plane can successfully + // reach the data plane components. + // When True, control plane has healthy connectivity to data plane nodes. + // When False, there are network connectivity issues preventing control plane from reaching the data plane. + // A failure here may indicate network policy issues, firewall rules, or infrastructure problems. + ControlPlaneToDataPlaneConnectivityHealthy ConditionType = "ControlPlaneToDataPlaneConnectivityHealthy" ) // Reasons. @@ -251,6 +258,12 @@ const ( RecoveryFinishedReason = "RecoveryFinished" CloudResourcesCleanupSkippedReason = "CloudResourcesCleanupSkipped" + + ControlPlaneToDataPlaneReasonNoKonnectivityAgentPodsFound = "NoKonnectivityAgentPodsFound" + + ControlPlaneToDataPlaneReasonLogAccessFailed = "ControlPlaneToDataPlaneLogAccessFailed" + + ControlPlaneToDataPlaneReasonNoWorkerNodesAvailable = "NoWorkerNodesAvailable" ) // Messages.