Repository: vmware-tanzu/nsx-operator
Pull request #1034: Enhance pod controller status update
Author: login: timdengyun
Labels: name: cla-not-required
Created: 2025-03-07T08:39:49Z | Merged: 2025-03-12T02:23:05Z

During the pod controller reconciles pod, it's helpful to expose error condition messages to the pod condition status, this helps users to understand what are the detailed error info.

In VCFA, podVM are common use case, so, it's worthy exposing more error message to users on pod status to help user understand how pod is running.

Test done:
1. Create VPC demo-vpc3 with connectivity profile without private-tgw ipblock configured. 
2. Create namespace:demo-ns3 with per-created vpc: demo-vpc3
3. Create podvM in the namespace demo-ns3, we see the error message returned from NSX exposed to pod condition status.
```
kubectl get pod -n demo-ns3
NAME                              READY   STATUS    RESTARTS   AGE
tea-deployment-7674dc65d6-bxxv6   0/1     Pending   0          14s
tea-deployment-7674dc65d6-h4c79   0/1     Pending   0          15s
```

```
kubectl get pod -n demo-ns3 tea-deployment-7674dc65d6-bxxv6 -o yaml
apiVersion: v1
kind: Pod
  generateName: tea-deployment-7674dc65d6-
..
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2025-03-07T09:07:12Z"
    status: "True"
    type: PodScheduled
  - lastProbeTime: null
    lastTransitionTime: "2025-03-07T09:07:13Z"
    message: 'error occurred while processing the Pod. Error: nsx error code: 610711,
      message: IPv4Address Private_TGW ip blocks are not configured in parent VPC.,
      details: , related error: nil'
    reason: PodNotReady
    status: "False"
    type: Ready
  phase: Pending
  qosClass: BestEffort
```
4. Update VPC connectivity profile and configured private tgw ipblock.
5. Check pod status again:
```
kubectl get pod -n demo-ns3
NAME                              READY   STATUS    RESTARTS   AGE
tea-deployment-7674dc65d6-bxxv6   1/1     Running   0          12m
tea-deployment-7674dc65d6-h4c79   1/1     Running   0          12m
```

```
kubectl get pod -n demo-ns3 tea-deployment-7674dc65d6-bxxv6 -o yaml
apiVersion: v1
kind: Pod
  generateName: tea-deployment-7674dc65d6-
..
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2025-03-07T09:07:12Z"
    status: "True"
    type: PodScheduled
  - lastProbeTime: null
    lastTransitionTime: "2025-03-07T09:19:03Z"
    message: Pod has been successfully created/updated
    reason: PodReady
    status: "True"
    type: Ready
  - lastProbeTime: null
    lastTransitionTime: "2025-03-07T09:19:03Z"
    status: "True"
    type: Initialized
  - lastProbeTime: null
    lastTransitionTime: "2025-03-07T09:19:16Z"
    message: 'containers with unready status: [tea]'
    reason: ContainersNotReady
    status: "True"
    type: ContainersReady
    
```


Diff:
diff --git a/pkg/controllers/pod/pod_controller.go b/pkg/controllers/pod/pod_controller.go
index 3b45ef35a..2e927b873 100644
--- a/pkg/controllers/pod/pod_controller.go
+++ b/pkg/controllers/pod/pod_controller.go
@@ -7,11 +7,13 @@ import (
 	"context"
 	"fmt"
 	"os"
+	"reflect"
 	"time"
 
 	"github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model"
 	v1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	apimachineryruntime "k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/client-go/tools/record"
@@ -45,6 +47,82 @@ type PodReconciler struct {
 	StatusUpdater     common.StatusUpdater
 }
 
+func setPodReadyStatusFalse(client client.Client, ctx context.Context, obj client.Object, transitionTime metav1.Time, err error, args ...interface{}) {
+	pod := obj.(*v1.Pod)
+	newConditions := []v1.PodCondition{
+		{
+			Type:   v1.PodReady,
+			Status: v1.ConditionFalse,
+			Message: fmt.Sprintf(
+				"error occurred while processing the Pod. Error: %v",
+				err,
+			),
+			Reason:             "PodNotReady",
+			LastTransitionTime: transitionTime,
+		},
+	}
+	updatePodStatusConditions(client, ctx, pod, newConditions)
+}
+
+func setPodReadyStatusTrue(client client.Client, ctx context.Context, obj client.Object, transitionTime metav1.Time, args ...interface{}) {
+	pod := obj.(*v1.Pod)
+	newConditions := []v1.PodCondition{
+		{
+			Type:               v1.PodReady,
+			Status:             v1.ConditionTrue,
+			Message:            "Pod has been successfully created/updated",
+			Reason:             "PodReady",
+			LastTransitionTime: transitionTime,
+		},
+	}
+	updatePodStatusConditions(client, ctx, pod, newConditions)
+}
+
+func updatePodStatusConditions(client client.Client, ctx context.Context, pod *v1.Pod, newConditions []v1.PodCondition) {
+	conditionsUpdated := false
+	for i := range newConditions {
+		if mergePodStatusCondition(pod, &newConditions[i]) {
+			conditionsUpdated = true
+		}
+	}
+
+	if conditionsUpdated {
+		err := client.Status().Update(ctx, pod)
+		if err != nil {
+			log.Error(err, "Failed to update Pod status", "Name", pod.Name, "Namespace", pod.Namespace)
+		}
+		log.V(2).Info("Updated pod", "Name", pod.Name, "Namespace", pod.Namespace,
+			"New Conditions", newConditions)
+	}
+}
+
+func mergePodStatusCondition(pod *v1.Pod, newCondition *v1.PodCondition) bool {
+	matchedCondition := getExistingConditionOfType(newCondition.Type, pod.Status.Conditions)
+
+	if reflect.DeepEqual(matchedCondition, newCondition) {
+		log.V(2).Info("Conditions already match", "New Condition", newCondition, "Existing Condition", matchedCondition)
+		return false
+	}
+
+	if matchedCondition != nil {
+		matchedCondition.Reason = newCondition.Reason
+		matchedCondition.Message = newCondition.Message
+		matchedCondition.Status = newCondition.Status
+	} else {
+		pod.Status.Conditions = append(pod.Status.Conditions, *newCondition)
+	}
+	return true
+}
+
+func getExistingConditionOfType(conditionType v1.PodConditionType, existingConditions []v1.PodCondition) *v1.PodCondition {
+	for i := range existingConditions {
+		if existingConditions[i].Type == conditionType {
+			return &existingConditions[i]
+		}
+	}
+	return nil
+}
+
 func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
 	log.Info("reconciling pod", "pod", req.NamespacedName)
 	startTime := time.Now()
@@ -77,6 +155,7 @@ func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R
 		isExisting, nsxSubnetPath, err := r.GetSubnetPathForPod(ctx, pod)
 		if err != nil {
 			log.Error(err, "failed to get NSX resource path from subnet", "pod.Name", pod.Name, "pod.UID", pod.UID)
+			r.StatusUpdater.UpdateFail(ctx, pod, err, "", setPodReadyStatusFalse)
 			return common.ResultRequeue, err
 		}
 		if !isExisting {
@@ -92,14 +171,15 @@ func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R
 		contextID := *node.UniqueId
 		nsxSubnet, err := r.SubnetService.GetSubnetByPath(nsxSubnetPath)
 		if err != nil {
+			r.StatusUpdater.UpdateFail(ctx, pod, err, "", setPodReadyStatusFalse)
 			return common.ResultRequeue, err
 		}
 		_, err = r.SubnetPortService.CreateOrUpdateSubnetPort(pod, nsxSubnet, contextID, &pod.ObjectMeta.Labels)
 		if err != nil {
-			r.StatusUpdater.UpdateFail(ctx, pod, err, "", nil)
+			r.StatusUpdater.UpdateFail(ctx, pod, err, "", setPodReadyStatusFalse)
 			return common.ResultRequeue, err
 		}
-		r.StatusUpdater.UpdateSuccess(ctx, pod, nil)
+		r.StatusUpdater.UpdateSuccess(ctx, pod, setPodReadyStatusTrue)
 	} else {
 		subnetPortID := r.SubnetPortService.BuildSubnetPortId(&pod.ObjectMeta)
 		if err := r.SubnetPortService.DeleteSubnetPortById(subnetPortID); err != nil {
diff --git a/pkg/controllers/pod/pod_controller_test.go b/pkg/controllers/pod/pod_controller_test.go
index 53ae7b8e2..7c2da36fb 100644
--- a/pkg/controllers/pod/pod_controller_test.go
+++ b/pkg/controllers/pod/pod_controller_test.go
@@ -28,19 +28,35 @@ import (
 	"github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/subnetport"
 )
 
-type fakeRecorder struct {
-}
+type fakeRecorder struct{}
 
 func (recorder fakeRecorder) Event(object runtime.Object, eventtype, reason, message string) {
 }
+
 func (recorder fakeRecorder) Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) {
 }
+
 func (recorder fakeRecorder) AnnotatedEventf(object runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) {
 }
 
+type fakeStatusWriter struct{}
+
+func (writer fakeStatusWriter) Create(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceCreateOption) error {
+	return nil
+}
+
+func (writer fakeStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
+	return nil
+}
+
+func (writer fakeStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error {
+	return nil
+}
+
 func TestPodReconciler_Reconcile(t *testing.T) {
 	mockCtl := gomock.NewController(t)
 	k8sClient := mock_client.NewMockClient(mockCtl)
+	fakewriter := fakeStatusWriter{}
 	defer mockCtl.Finish()
 	r := &PodReconciler{
 		Client: k8sClient,
@@ -99,10 +115,23 @@ func TestPodReconciler_Reconcile(t *testing.T) {
 					podCR.Spec.NodeName = "node-1"
 					return nil
 				})
+
 				patchesGetSubnetPathForPod := gomonkey.ApplyFunc((*PodReconciler).GetSubnetPathForPod,
 					func(r *PodReconciler, ctx context.Context, pod *v1.Pod) (bool, string, error) {
 						return false, "", errors.New("failed to get subnet path")
 					})
+
+				k8sClient.EXPECT().Status().Return(fakewriter).AnyTimes()
+				patchesGetSubnetPathForPod.ApplyFunc(fakeStatusWriter.Update,
+					func(writer fakeStatusWriter, ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
+						pod := obj.(*v1.Pod)
+						assert.Equal(t, "error occurred while processing the Pod. Error: failed to get subnet path", pod.Status.Conditions[0].Message)
+						assert.Equal(t, "PodNotReady", pod.Status.Conditions[0].Reason)
+						assert.Equal(t, v1.ConditionFalse, pod.Status.Conditions[0].Status)
+						assert.Equal(t, v1.PodReady, pod.Status.Conditions[0].Type)
+						return nil
+					})
+
 				return patchesGetSubnetPathForPod
 			},
 			expectedErr:    "failed to get subnet path",
@@ -157,6 +186,17 @@ func TestPodReconciler_Reconcile(t *testing.T) {
 					func(r *subnet.SubnetService, path string) (*model.VpcSubnet, error) {
 						return nil, errors.New("failed to get subnet")
 					})
+
+				k8sClient.EXPECT().Status().Return(fakewriter).AnyTimes()
+				patches.ApplyFunc(fakeStatusWriter.Update,
+					func(writer fakeStatusWriter, ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
+						pod := obj.(*v1.Pod)
+						assert.Equal(t, "error occurred while processing the Pod. Error: failed to get subnet", pod.Status.Conditions[0].Message)
+						assert.Equal(t, "PodNotReady", pod.Status.Conditions[0].Reason)
+						assert.Equal(t, v1.ConditionFalse, pod.Status.Conditions[0].Status)
+						assert.Equal(t, v1.PodReady, pod.Status.Conditions[0].Type)
+						return nil
+					})
 				return patches
 			},
 			expectedErr:    "failed to get subnet",
@@ -186,6 +226,18 @@ func TestPodReconciler_Reconcile(t *testing.T) {
 					func(r *subnetport.SubnetPortService, obj interface{}, nsxSubnet *model.VpcSubnet, contextID string, tags *map[string]string) (*model.SegmentPortState, error) {
 						return nil, errors.New("failed to create subnetport")
 					})
+
+				k8sClient.EXPECT().Status().Return(fakewriter).AnyTimes()
+				patches.ApplyFunc(fakeStatusWriter.Update,
+					func(writer fakeStatusWriter, ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
+						pod := obj.(*v1.Pod)
+						assert.Equal(t, "error occurred while processing the Pod. Error: failed to create subnetport", pod.Status.Conditions[0].Message)
+						assert.Equal(t, "PodNotReady", pod.Status.Conditions[0].Reason)
+						assert.Equal(t, v1.ConditionFalse, pod.Status.Conditions[0].Status)
+						assert.Equal(t, v1.PodReady, pod.Status.Conditions[0].Type)
+						return nil
+					})
+
 				return patches
 			},
 			expectedErr:    "failed to create subnetport",
@@ -215,6 +267,17 @@ func TestPodReconciler_Reconcile(t *testing.T) {
 					func(s *subnetport.SubnetPortService, obj interface{}, nsxSubnet *model.VpcSubnet, contextID string, tags *map[string]string) (*model.SegmentPortState, error) {
 						return &model.SegmentPortState{}, nil
 					})
+
+				k8sClient.EXPECT().Status().Return(fakewriter).AnyTimes()
+				patches.ApplyFunc(fakeStatusWriter.Update,
+					func(writer fakeStatusWriter, ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
+						pod := obj.(*v1.Pod)
+						assert.Equal(t, "Pod has been successfully created/updated", pod.Status.Conditions[0].Message)
+						assert.Equal(t, "PodReady", pod.Status.Conditions[0].Reason)
+						assert.Equal(t, v1.ConditionTrue, pod.Status.Conditions[0].Status)
+						assert.Equal(t, v1.PodReady, pod.Status.Conditions[0].Type)
+						return nil
+					})
 				return patches
 			},
 			expectedResult: common.ResultNormal,


Reviews:
user: login: yanjunz97
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T06:26:10Z
user: login: timdengyun
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T09:37:31Z
user: login: timdengyun
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T09:39:22Z
user: login: timdengyun
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T09:39:31Z
user: login: timdengyun
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T09:39:40Z
user: login: yanjunz97
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T10:17:43Z
user: login: TaoZou1
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T02:17:02Z

Review comments:
diff_hunk: @@ -45,6 +47,82 @@ type PodReconciler struct {
 	StatusUpdater     common.StatusUpdater
 }
 
+func setPodReadyStatusFalse(client client.Client, ctx context.Context, obj client.Object, transitionTime metav1.Time, err error, args ...interface{}) {
+	pod := obj.(*v1.Pod)
+	newConditions := []v1.PodCondition{
+		{
+			Type:   v1.PodReady,
+			Status: v1.ConditionFalse,
+			Message: fmt.Sprintf(
+				"error occurred while processing the Pod. Error: %v",
+				err,
+			),
+			Reason:             "PodNotReady",
+			LastTransitionTime: transitionTime,
+		},
+	}
+	updatePodStatusConditions(client, ctx, pod, newConditions)
+}
+
+func setPodReadyStatusTrue(client client.Client, ctx context.Context, obj client.Object, transitionTime metav1.Time, args ...interface{}) {
+	pod := obj.(*v1.Pod)
+	newConditions := []v1.PodCondition{
+		{
+			Type:               v1.PodReady,
+			Status:             v1.ConditionTrue,
+			Message:            "Pod has been successfully created/updated",
+			Reason:             "PodReady",
+			LastTransitionTime: transitionTime,
+		},
+	}
+	updatePodStatusConditions(client, ctx, pod, newConditions)
+}
+
+func updatePodStatusConditions(client client.Client, ctx context.Context, pod *v1.Pod, newConditions []v1.PodCondition) {
+	conditionsUpdated := false
+	for i := range newConditions {
+		if mergePodStatusCondition(pod, &newConditions[i]) {
+			conditionsUpdated = true
+		}
+	}
+
+	if conditionsUpdated {
+		err := client.Status().Update(ctx, pod)
+		if err != nil {
+			log.Error(err, "Failed to update Pod status", "Name", pod.Name, "Namespace", pod.Namespace)
+		}
+		log.V(2).Info("updated pod", "Name", pod.Name, "Namespace", pod.Namespace,
path: pkg/controllers/pod/pod_controller.go
user: login: yanjunz97
body: ```suggestion
		log.V(2).Info("Updated Pod", "Name", pod.Name, "Namespace", pod.Namespace,
```
created_at: 2025-03-10T06:16:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -45,6 +47,82 @@ type PodReconciler struct {
 	StatusUpdater     common.StatusUpdater
 }
 
+func setPodReadyStatusFalse(client client.Client, ctx context.Context, obj client.Object, transitionTime metav1.Time, err error, args ...interface{}) {
+	pod := obj.(*v1.Pod)
+	newConditions := []v1.PodCondition{
+		{
+			Type:   v1.PodReady,
+			Status: v1.ConditionFalse,
+			Message: fmt.Sprintf(
+				"error occurred while processing the Pod. Error: %v",
+				err,
+			),
+			Reason:             "PodNotReady",
+			LastTransitionTime: transitionTime,
+		},
+	}
+	updatePodStatusConditions(client, ctx, pod, newConditions)
+}
+
+func setPodReadyStatusTrue(client client.Client, ctx context.Context, obj client.Object, transitionTime metav1.Time, args ...interface{}) {
+	pod := obj.(*v1.Pod)
+	newConditions := []v1.PodCondition{
+		{
+			Type:               v1.PodReady,
+			Status:             v1.ConditionTrue,
+			Message:            "Pod has been successfully created/updated",
+			Reason:             "PodReady",
+			LastTransitionTime: transitionTime,
+		},
+	}
+	updatePodStatusConditions(client, ctx, pod, newConditions)
+}
+
+func updatePodStatusConditions(client client.Client, ctx context.Context, pod *v1.Pod, newConditions []v1.PodCondition) {
+	conditionsUpdated := false
+	for i := range newConditions {
+		if mergePodStatusCondition(pod, &newConditions[i]) {
+			conditionsUpdated = true
+		}
+	}
+
+	if conditionsUpdated {
+		err := client.Status().Update(ctx, pod)
+		if err != nil {
+			log.Error(err, "Failed to update Pod status", "Name", pod.Name, "Namespace", pod.Namespace)
+		}
+		log.V(2).Info("updated pod", "Name", pod.Name, "Namespace", pod.Namespace,
+			"New Conditions", newConditions)
+	}
+}
+
+func mergePodStatusCondition(pod *v1.Pod, newCondition *v1.PodCondition) bool {
+	matchedCondition := getExistingConditionOfType(newCondition.Type, pod.Status.Conditions)
+
+	if reflect.DeepEqual(matchedCondition, newCondition) {
+		log.V(2).Info("conditions already match", "New Condition", newCondition, "Existing Condition", matchedCondition)
path: pkg/controllers/pod/pod_controller.go
user: login: yanjunz97
body: ```suggestion
		log.V(2).Info("Conditions already match", "New Condition", newCondition, "Existing Condition", matchedCondition)
```
created_at: 2025-03-10T06:17:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,10 +175,10 @@ func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R
 		}
 		_, err = r.SubnetPortService.CreateOrUpdateSubnetPort(pod, nsxSubnet, contextID, &pod.ObjectMeta.Labels)
 		if err != nil {
-			r.StatusUpdater.UpdateFail(ctx, pod, err, "", nil)
+			r.StatusUpdater.UpdateFail(ctx, pod, err, "", setPodReadyStatusFalse, nil)
path: pkg/controllers/pod/pod_controller.go
user: login: yanjunz97
body: Do we plan to update Pod status for those 2 errors:
https://github.com/vmware-tanzu/nsx-operator/blob/fff9bc7fbe104fb9e42f3f2dc93ce7bca37ef20c/pkg/controllers/pod/pod_controller.go#L87-L91
https://github.com/vmware-tanzu/nsx-operator/blob/fff9bc7fbe104fb9e42f3f2dc93ce7bca37ef20c/pkg/controllers/pod/pod_controller.go#L93-L96
created_at: 2025-03-10T06:21:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,6 +155,7 @@ func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R
 		isExisting, nsxSubnetPath, err := r.GetSubnetPathForPod(ctx, pod)
 		if err != nil {
 			log.Error(err, "failed to get NSX resource path from subnet", "pod.Name", pod.Name, "pod.UID", pod.UID)
+			r.StatusUpdater.UpdateFail(ctx, pod, err, "", setPodReadyStatusFalse, nil)
path: pkg/controllers/pod/pod_controller.go
user: login: yanjunz97
body: Maybe just `r.StatusUpdater.UpdateFail(ctx, pod, err, "", setPodReadyStatusFalse)` ?
created_at: 2025-03-10T06:22:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,10 +175,10 @@ func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R
 		}
 		_, err = r.SubnetPortService.CreateOrUpdateSubnetPort(pod, nsxSubnet, contextID, &pod.ObjectMeta.Labels)
 		if err != nil {
-			r.StatusUpdater.UpdateFail(ctx, pod, err, "", nil)
+			r.StatusUpdater.UpdateFail(ctx, pod, err, "", setPodReadyStatusFalse, nil)
path: pkg/controllers/pod/pod_controller.go
user: login: yanjunz97
body: ditto
created_at: 2025-03-10T06:22:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,10 +175,10 @@ func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R
 		}
 		_, err = r.SubnetPortService.CreateOrUpdateSubnetPort(pod, nsxSubnet, contextID, &pod.ObjectMeta.Labels)
 		if err != nil {
-			r.StatusUpdater.UpdateFail(ctx, pod, err, "", nil)
+			r.StatusUpdater.UpdateFail(ctx, pod, err, "", setPodReadyStatusFalse, nil)
 			return common.ResultRequeue, err
 		}
-		r.StatusUpdater.UpdateSuccess(ctx, pod, nil)
+		r.StatusUpdater.UpdateSuccess(ctx, pod, setPodReadyStatusTrue, nil)
path: pkg/controllers/pod/pod_controller.go
user: login: yanjunz97
body: ditto
created_at: 2025-03-10T06:22:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -45,6 +47,82 @@ type PodReconciler struct {
 	StatusUpdater     common.StatusUpdater
 }
 
+func setPodReadyStatusFalse(client client.Client, ctx context.Context, obj client.Object, transitionTime metav1.Time, err error, args ...interface{}) {
+	pod := obj.(*v1.Pod)
+	newConditions := []v1.PodCondition{
+		{
+			Type:   v1.PodReady,
+			Status: v1.ConditionFalse,
+			Message: fmt.Sprintf(
+				"error occurred while processing the Pod. Error: %v",
+				err,
+			),
+			Reason:             "PodNotReady",
+			LastTransitionTime: transitionTime,
+		},
+	}
+	updatePodStatusConditions(client, ctx, pod, newConditions)
+}
+
+func setPodReadyStatusTrue(client client.Client, ctx context.Context, obj client.Object, transitionTime metav1.Time, args ...interface{}) {
+	pod := obj.(*v1.Pod)
+	newConditions := []v1.PodCondition{
+		{
+			Type:               v1.PodReady,
+			Status:             v1.ConditionTrue,
+			Message:            "Pod has been successfully created/updated",
+			Reason:             "PodReady",
+			LastTransitionTime: transitionTime,
+		},
+	}
+	updatePodStatusConditions(client, ctx, pod, newConditions)
+}
+
+func updatePodStatusConditions(client client.Client, ctx context.Context, pod *v1.Pod, newConditions []v1.PodCondition) {
+	conditionsUpdated := false
+	for i := range newConditions {
+		if mergePodStatusCondition(pod, &newConditions[i]) {
+			conditionsUpdated = true
+		}
+	}
+
+	if conditionsUpdated {
+		err := client.Status().Update(ctx, pod)
+		if err != nil {
+			log.Error(err, "Failed to update Pod status", "Name", pod.Name, "Namespace", pod.Namespace)
+		}
+		log.V(2).Info("updated pod", "Name", pod.Name, "Namespace", pod.Namespace,
path: pkg/controllers/pod/pod_controller.go
user: login: timdengyun
body: Thanks, fixed
created_at: 2025-03-11T09:37:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,10 +175,10 @@ func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R
 		}
 		_, err = r.SubnetPortService.CreateOrUpdateSubnetPort(pod, nsxSubnet, contextID, &pod.ObjectMeta.Labels)
 		if err != nil {
-			r.StatusUpdater.UpdateFail(ctx, pod, err, "", nil)
+			r.StatusUpdater.UpdateFail(ctx, pod, err, "", setPodReadyStatusFalse, nil)
path: pkg/controllers/pod/pod_controller.go
user: login: timdengyun
body: I added handling for the error from `r.SubnetService.GetSubnetByPath(nsxSubnetPath) `
but for the error from get node ID for pod, since there are some expected errors in the controller start, I feel that we don't need to add this error in pod status message.
created_at: 2025-03-11T09:39:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,6 +155,7 @@ func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R
 		isExisting, nsxSubnetPath, err := r.GetSubnetPathForPod(ctx, pod)
 		if err != nil {
 			log.Error(err, "failed to get NSX resource path from subnet", "pod.Name", pod.Name, "pod.UID", pod.UID)
+			r.StatusUpdater.UpdateFail(ctx, pod, err, "", setPodReadyStatusFalse, nil)
path: pkg/controllers/pod/pod_controller.go
user: login: timdengyun
body: Thanks, fixed. 
created_at: 2025-03-11T09:39:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,10 +175,10 @@ func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R
 		}
 		_, err = r.SubnetPortService.CreateOrUpdateSubnetPort(pod, nsxSubnet, contextID, &pod.ObjectMeta.Labels)
 		if err != nil {
-			r.StatusUpdater.UpdateFail(ctx, pod, err, "", nil)
+			r.StatusUpdater.UpdateFail(ctx, pod, err, "", setPodReadyStatusFalse, nil)
 			return common.ResultRequeue, err
 		}
-		r.StatusUpdater.UpdateSuccess(ctx, pod, nil)
+		r.StatusUpdater.UpdateSuccess(ctx, pod, setPodReadyStatusTrue, nil)
path: pkg/controllers/pod/pod_controller.go
user: login: timdengyun
body: Thanks, fixed. 
created_at: 2025-03-11T09:39:40Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov-commenter
created_at: 2025-03-07T17:13:31Z
body: ## [Codecov](https://app.codecov.io/gh/vmware-tanzu/nsx-operator/pull/1034?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vmware-tanzu) Report
:x: Patch coverage is `75.38462%` with `16 lines` in your changes missing coverage. Please review.
:white_check_mark: Project coverage is 74.12%. Comparing base ([`a0012b8`](https://app.codecov.io/gh/vmware-tanzu/nsx-operator/commit/a0012b893d8fd9658b7dec32cf333944dcdb0ca1?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vmware-tanzu)) to head ([`e282841`](https://app.codecov.io/gh/vmware-tanzu/nsx-operator/commit/e28284153ce6ef2be9a8c52c4b98ae303ee68868?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vmware-tanzu)).
:warning: Report is 313 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/vmware-tanzu/nsx-operator/pull/1034?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vmware-tanzu) | Patch % | Lines |
|---|---|---|
| [pkg/controllers/pod/pod\_controller.go](https://app.codecov.io/gh/vmware-tanzu/nsx-operator/pull/1034?src=pr&el=tree&filepath=pkg%2Fcontrollers%2Fpod%2Fpod_controller.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vmware-tanzu#diff-cGtnL2NvbnRyb2xsZXJzL3BvZC9wb2RfY29udHJvbGxlci5nbw==) | 75.38% | [11 Missing and 5 partials :warning: ](https://app.codecov.io/gh/vmware-tanzu/nsx-operator/pull/1034?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vmware-tanzu) |

<details><summary>Additional details and impacted files</summary>



[![Impacted file tree graph](https://app.codecov.io/gh/vmware-tanzu/nsx-operator/pull/1034/graphs/tree.svg?width=650&height=150&src=pr&token=R0Q6IGC2PC&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vmware-tanzu)](https://app.codecov.io/gh/vmware-tanzu/nsx-operator/pull/1034?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vmware-tanzu)

```diff
@@           Coverage Diff           @@
##             main    #1034   +/-   ##
=======================================
  Coverage   74.12%   74.12%           
=======================================
  Files         118      118           
  Lines       16461    16524   +63     
=======================================
+ Hits        12202    12249   +47     
- Misses       3463     3474   +11     
- Partials      796      801    +5     
```

| [Flag](https://app.codecov.io/gh/vmware-tanzu/nsx-operator/pull/1034/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vmware-tanzu) | Coverage Δ | |
|---|---|---|
| [unit-tests](https://app.codecov.io/gh/vmware-tanzu/nsx-operator/pull/1034/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vmware-tanzu) | `∅ <75.38%> (∅)` | |

| [Files with missing lines](https://app.codecov.io/gh/vmware-tanzu/nsx-operator/pull/1034?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vmware-tanzu) | Coverage Δ | |
|---|---|---|
| [pkg/controllers/pod/pod\_controller.go](https://app.codecov.io/gh/vmware-tanzu/nsx-operator/pull/1034?src=pr&el=tree&filepath=pkg%2Fcontrollers%2Fpod%2Fpod_controller.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vmware-tanzu#diff-cGtnL2NvbnRyb2xsZXJzL3BvZC9wb2RfY29udHJvbGxlci5nbw==) | `65.30% <75.38%> (+3.21%)` | :arrow_up: |
</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: timdengyun
created_at: 2025-03-07T17:14:44Z
body: /e2e
author_association: CONTRIBUTOR
user: login: timdengyun
created_at: 2025-03-10T05:51:16Z
body: /e2e
author_association: CONTRIBUTOR
user: login: zhengxiexie
created_at: 2025-03-11T09:49:51Z
body: /e2e
author_association: CONTRIBUTOR
user: login: zhengxiexie
created_at: 2025-03-11T09:53:41Z
body: /e2e
author_association: CONTRIBUTOR
user: login: timdengyun
created_at: 2025-03-11T16:49:23Z
body: /e2e
author_association: CONTRIBUTOR
user: login: timdengyun
created_at: 2025-03-11T16:51:58Z
body: /e2e
author_association: CONTRIBUTOR