-
Notifications
You must be signed in to change notification settings - Fork 566
CNTRLPLANE-1956: add logic to check control-plane to data-plane connectivity #7260
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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.DataPlaneConnectionAvailable) + " updated") | ||
| return nil | ||
| } | ||
|
|
||
| condition := &metav1.Condition{ | ||
| Type: string(hyperv1.DataPlaneConnectionAvailable), | ||
| Status: metav1.ConditionFalse, // False by default | ||
| } | ||
| totalNodes, err := util.CountAvailableNodes(ctx, r.client) | ||
| if err != nil { | ||
| condition.Status = metav1.ConditionUnknown | ||
| condition.Reason = hyperv1.ReconcileErrorReason | ||
| condition.Message = "Unable to count worker nodes: " + err.Error() | ||
| return patchHCPWithCondition(hcp, condition) | ||
| } | ||
| if totalNodes == 0 { | ||
| condition.Status = metav1.ConditionUnknown | ||
| condition.Reason = hyperv1.DataPlaneConnectionNoWorkerNodesAvailableReason | ||
| 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.ReconciliationErrorReason | ||
| 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.DataPlaneConnectionNoKonnectivityAgentPodsNotFoundReason | ||
| condition.Message = "Couldn't find any konnectivity-agent running in data plane" | ||
| } else { | ||
| if !logsFound { | ||
| condition.Reason = hyperv1.DataPlaneConnectionNoKonnectivityAgentPodsNotFoundReason | ||
| condition.Message = "failed to read konnectivity-agent logs from data plane" | ||
| } else { | ||
| condition.Status = metav1.ConditionTrue | ||
| condition.Reason = hyperv1.AsExpectedReason | ||
| condition.Message = hyperv1.AllIsWellMessage | ||
| } | ||
| } | ||
|
Comment on lines
+1474
to
+1486
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix semantic mismatch: pods found but logs unreadable should use LogsAccessFailedReason When
This is misleading—the reason says "pods not found" but the scenario is "running pods exist, logs couldn't be read." The reason should reflect the actual failure mode. Apply this diff: if !runningPodsFound {
condition.Reason = hyperv1.DataPlaneConnectionNoKonnectivityAgentPodsNotFoundReason
condition.Message = "Couldn't find any konnectivity-agent running in data plane"
} else {
if !logsFound {
- condition.Reason = hyperv1.DataPlaneConnectionNoKonnectivityAgentPodsNotFoundReason
+ condition.Reason = hyperv1.DataPlaneConnectionLogsAccessFailedReason
condition.Message = "failed to read konnectivity-agent logs from data plane"
} else {
condition.Status = metav1.ConditionTrue
condition.Reason = hyperv1.AsExpectedReason
condition.Message = hyperv1.AllIsWellMessage
}
}🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @coderabbitai ignore it
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sdminonne Understood, I'll skip this suggestion. Let me know if you need anything else! |
||
|
|
||
| return patchHCPWithCondition(hcp, condition) | ||
| } | ||
|
Comment on lines
+1418
to
+1489
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo in reason constant and pod-list error semantics There are two issues in
You can fix both with a small change: 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.ReconciliationErrorReason
- condition.Message = "Couldn't list konnectivity-agent PODs in kube-system namespace: " + err.Error()
+ condition.Status = metav1.ConditionUnknown
+ condition.Reason = hyperv1.ReconcileErrorReason
+ condition.Message = "Couldn't list konnectivity-agent PODs in kube-system namespace: " + err.Error()
return patchHCPWithCondition(hcp, condition)
}This both restores compilation (correct constant) and keeps the condition semantics consistent with the earlier 🤖 Prompt for AI Agents |
||
|
|
||
| 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 { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Align connectivity condition semantics when konnectivity-agent pod listing fails
The core logic of
reconcileControlPlaneDataPlaneConnectivityConditionsis solid (nodes → pods → logs), but the pod-list failure branch currently misreports the reason:Here, we don’t know whether pods exist; we just failed to list them (RBAC, connectivity, API outage, etc.). Surfacing this as
NoKonnectivityAgentPodsFoundis misleading for operators and any automation keying onReason.To better reflect the failure mode and match how other “can’t determine connectivity” cases are handled:
ControlPlaneToDataPlaneReasonLogAccessFailedis less misleading than claiming “no pods found”).Concrete tweak:
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) + condition.Status = metav1.ConditionUnknown + condition.Reason = hyperv1.ControlPlaneToDataPlaneReasonLogAccessFailed + condition.Message = "Failed to list konnectivity-agent PODs in kube-system namespace: " + err.Error() + return patchHCPWithCondition(hcp, condition) }This keeps the rest of your paths intact:
Unknown / NoWorkerNodesAvailablefor zero workers.False / NoKonnectivityAgentPodsFoundfor zero running pods.False / LogAccessFailedwhen running pods exist but logs can’t be read or are empty.True / AsExpected+AllIsWellMessagewhen at least one running pod has readable logs.If you adopt this, it’s worth adding a small test case in
Test_reconciler_reconcileControlPlaneDataPlaneConnectivityConditionsthat exercises the “list pods fails” path (e.g., by wrappingr.uncachedClientwith a client that returns an error onList) to lock the semantics in.Also applies to: 1418-1485
🤖 Prompt for AI Agents