From 6c2be8b737801cfa30bebddbdc9f37051a8851ab Mon Sep 17 00:00:00 2001 From: Rohit Kumar Date: Wed, 10 Dec 2025 16:32:53 +0530 Subject: [PATCH 1/3] update `lastTransitionTime` for volume and nic in the machineCondition --- .../controllers/machine_controller.go | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/poollet/machinepoollet/controllers/machine_controller.go b/poollet/machinepoollet/controllers/machine_controller.go index f8786db84..484026789 100644 --- a/poollet/machinepoollet/controllers/machine_controller.go +++ b/poollet/machinepoollet/controllers/machine_controller.go @@ -565,22 +565,38 @@ func (r *MachineReconciler) computeVolumesReadyCondition(volumeStatuses []comput } status, reason, message := corev1.ConditionTrue, "VolumesReady", "All volumes are ready" + var lastTransitionTime *metav1.Time for _, vs := range volumeStatuses { if vs.State != computev1alpha1.VolumeStateAttached { status = corev1.ConditionFalse reason = fmt.Sprintf("VolumeNotReady: %s", vs.Name) message = fmt.Sprintf("Volume %s is not attached (state: %s)", vs.Name, vs.State) + lastTransitionTime = vs.LastStateTransitionTime break } } + if status == corev1.ConditionTrue { + for _, vs := range volumeStatuses { + if vs.LastStateTransitionTime != nil { + lastTransitionTime = vs.LastStateTransitionTime + break + } + } + } + + transitionTime := now + if lastTransitionTime != nil { + transitionTime = *lastTransitionTime + } + return computev1alpha1.MachineCondition{ Type: computev1alpha1.MachineConditionType("VolumesReady"), Status: status, Reason: reason, Message: message, - LastTransitionTime: now, + LastTransitionTime: transitionTime, } } @@ -590,22 +606,38 @@ func (r *MachineReconciler) computeNetworkInterfacesReadyCondition(nicStatuses [ } status, reason, message := corev1.ConditionTrue, "NetworkInterfacesReady", "All network interfaces are ready" + var lastTransitionTime *metav1.Time for _, nicStatus := range nicStatuses { if nicStatus.State != computev1alpha1.NetworkInterfaceStateAttached { status = corev1.ConditionFalse reason = fmt.Sprintf("NetworkInterfaceNotReady: %s", nicStatus.Name) message = fmt.Sprintf("Network interface %s is not attached (state: %s)", nicStatus.Name, nicStatus.State) + lastTransitionTime = nicStatus.LastStateTransitionTime break } } + if status == corev1.ConditionTrue { + for _, nicStatus := range nicStatuses { + if nicStatus.LastStateTransitionTime != nil { + lastTransitionTime = nicStatus.LastStateTransitionTime + break + } + } + } + + transitionTime := now + if lastTransitionTime != nil { + transitionTime = *lastTransitionTime + } + return computev1alpha1.MachineCondition{ Type: computev1alpha1.MachineConditionType("NetworkInterfacesReady"), Status: status, Reason: reason, Message: message, - LastTransitionTime: now, + LastTransitionTime: transitionTime, } } From 09731e710be76cc011024b0e5ef4e4583efb32d6 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Date: Fri, 12 Dec 2025 19:21:01 +0530 Subject: [PATCH 2/3] update machineConditions to persist the conditions history --- .../controllers/machine_controller.go | 43 ++++++++++++++++--- .../controllers/machine_controller_test.go | 14 +++--- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/poollet/machinepoollet/controllers/machine_controller.go b/poollet/machinepoollet/controllers/machine_controller.go index 484026789..7d5aa39ea 100644 --- a/poollet/machinepoollet/controllers/machine_controller.go +++ b/poollet/machinepoollet/controllers/machine_controller.go @@ -504,7 +504,8 @@ func (r *MachineReconciler) updateMachineStatus(ctx context.Context, machine *co machine.Status.ObservedGeneration = generation machine.Status.Volumes = volumeStatuses machine.Status.NetworkInterfaces = nicStatuses - machine.Status.Conditions = r.computeMachineConditions(state, volumeStatuses, nicStatuses, now) + computedConds := r.computeMachineConditions(state, volumeStatuses, nicStatuses, now) + machine.Status.Conditions = r.mergeMachineConditions(machine.Status.Conditions, computedConds) if err := r.Status().Patch(ctx, machine, client.MergeFrom(base)); err != nil { return fmt.Errorf("error patching status: %w", err) @@ -551,7 +552,7 @@ func (r *MachineReconciler) computeMachineReadyCondition(state computev1alpha1.M } return computev1alpha1.MachineCondition{ - Type: "Ready", + Type: "MachineReady", Status: status, Reason: reason, Message: message, @@ -568,10 +569,15 @@ func (r *MachineReconciler) computeVolumesReadyCondition(volumeStatuses []comput var lastTransitionTime *metav1.Time for _, vs := range volumeStatuses { + volume := &vs.VolumeRef + volName := volume.Name + if volName == "" { //if local-disk volume is used, where no actual volume resource exists + volName = vs.Name + } if vs.State != computev1alpha1.VolumeStateAttached { status = corev1.ConditionFalse - reason = fmt.Sprintf("VolumeNotReady: %s", vs.Name) - message = fmt.Sprintf("Volume %s is not attached (state: %s)", vs.Name, vs.State) + reason = fmt.Sprintf("VolumeNotReady: %s", volName) + message = fmt.Sprintf("Volume %s is not attached (state: %s)", volName, vs.State) lastTransitionTime = vs.LastStateTransitionTime break } @@ -609,10 +615,11 @@ func (r *MachineReconciler) computeNetworkInterfacesReadyCondition(nicStatuses [ var lastTransitionTime *metav1.Time for _, nicStatus := range nicStatuses { + nic := &nicStatus.NetworkInterfaceRef if nicStatus.State != computev1alpha1.NetworkInterfaceStateAttached { status = corev1.ConditionFalse - reason = fmt.Sprintf("NetworkInterfaceNotReady: %s", nicStatus.Name) - message = fmt.Sprintf("Network interface %s is not attached (state: %s)", nicStatus.Name, nicStatus.State) + reason = fmt.Sprintf("NetworkInterfaceNotReady: %s", nic.Name) + message = fmt.Sprintf("Network interface %s is not attached (state: %s)", nic.Name, nicStatus.State) lastTransitionTime = nicStatus.LastStateTransitionTime break } @@ -641,6 +648,28 @@ func (r *MachineReconciler) computeNetworkInterfacesReadyCondition(nicStatuses [ } } +func (r *MachineReconciler) mergeMachineConditions( + existing []computev1alpha1.MachineCondition, + current []computev1alpha1.MachineCondition, +) []computev1alpha1.MachineCondition { + updated := append([]computev1alpha1.MachineCondition{}, existing...) + + lastStatus := make(map[string]corev1.ConditionStatus, len(existing)) + for _, c := range existing { + lastStatus[string(c.Type)] = c.Status + } + + for _, newCond := range current { + conditionType := string(newCond.Type) + if prev, ok := lastStatus[conditionType]; !ok || prev != newCond.Status { + updated = append(updated, newCond) + lastStatus[conditionType] = newCond.Status + } + } + + return updated +} + func (r *MachineReconciler) prepareIRIPower(power computev1alpha1.Power) (iri.Power, error) { switch power { case computev1alpha1.PowerOn: @@ -990,7 +1019,7 @@ func (r *MachineReconciler) enqueueMachinesReferencingNetworkInterface() handler }, r.matchingWatchLabel(), ); err != nil { - log.Error(err, "Error listing machines using secret", "NetworkInterfaceKey", client.ObjectKeyFromObject(nic)) + log.Error(err, "Error listing machines using network interface", "NetworkInterfaceKey", client.ObjectKeyFromObject(nic)) return nil } diff --git a/poollet/machinepoollet/controllers/machine_controller_test.go b/poollet/machinepoollet/controllers/machine_controller_test.go index 20c05728e..2fecd6c7a 100644 --- a/poollet/machinepoollet/controllers/machine_controller_test.go +++ b/poollet/machinepoollet/controllers/machine_controller_test.go @@ -197,7 +197,7 @@ var _ = Describe("MachineController", func() { )) Eventually(Object(machine)).Should(SatisfyAll( HaveField("Status.Conditions", ContainElement(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(computev1alpha1.MachineConditionType("Ready")), + "Type": Equal(computev1alpha1.MachineConditionType("MachineReady")), "Status": Equal(corev1.ConditionFalse), "Reason": Equal("Pending"), "Message": Equal("Machine is pending"), @@ -206,15 +206,15 @@ var _ = Describe("MachineController", func() { HaveField("Status.Conditions", ContainElement(MatchFields(IgnoreExtras, Fields{ "Type": Equal(computev1alpha1.MachineConditionType("VolumesReady")), "Status": Equal(corev1.ConditionFalse), - "Reason": Equal(fmt.Sprintf("VolumeNotReady: %s", "primary")), - "Message": Equal(fmt.Sprintf("Volume %s is not attached (state: %s)", "primary", "Pending")), + "Reason": Equal(fmt.Sprintf("VolumeNotReady: %s", volume.Name)), + "Message": Equal(fmt.Sprintf("Volume %s is not attached (state: %s)", volume.Name, "Pending")), "LastTransitionTime": Not(BeNil()), }))), HaveField("Status.Conditions", ContainElement(MatchFields(IgnoreExtras, Fields{ "Type": Equal(computev1alpha1.MachineConditionType("NetworkInterfacesReady")), "Status": Equal(corev1.ConditionFalse), - "Reason": Equal(fmt.Sprintf("NetworkInterfaceNotReady: %s", "primary")), - "Message": Equal(fmt.Sprintf("Network interface %s is not attached (state: %s)", "primary", "Pending")), + "Reason": Equal(fmt.Sprintf("NetworkInterfaceNotReady: %s", nic.Name)), + "Message": Equal(fmt.Sprintf("Network interface %s is not attached (state: %s)", nic.Name, "Pending")), "LastTransitionTime": Not(BeNil()), }))), )) @@ -507,7 +507,7 @@ var _ = Describe("MachineController", func() { By("waiting for the machine conditions to be updated") Eventually(Object(machine)).Should(SatisfyAll( HaveField("Status.Conditions", ContainElement(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(computev1alpha1.MachineConditionType("Ready")), + "Type": Equal(computev1alpha1.MachineConditionType("MachineReady")), "Status": Equal(corev1.ConditionTrue), "Reason": Equal("Running"), "Message": Equal("Machine is running"), @@ -527,7 +527,7 @@ var _ = Describe("MachineController", func() { By("waiting for the machine conditions to be updated") Eventually(Object(machine)).Should(SatisfyAll( HaveField("Status.Conditions", ContainElement(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(computev1alpha1.MachineConditionType("Ready")), + "Type": Equal(computev1alpha1.MachineConditionType("MachineReady")), "Status": Equal(corev1.ConditionFalse), "Reason": Equal("Terminating"), "Message": Equal("Machine is terminating or terminated"), From dd0b99642dce9749388abd64ab78f3d42ae4ecfc Mon Sep 17 00:00:00 2001 From: Rohit Kumar Date: Fri, 9 Jan 2026 14:05:12 +0530 Subject: [PATCH 3/3] address review comments --- api/compute/v1alpha1/machine_types.go | 9 ++++ .../controllers/machine_controller.go | 43 +++++++++++++++---- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/api/compute/v1alpha1/machine_types.go b/api/compute/v1alpha1/machine_types.go index bd07f6dab..6b624267a 100644 --- a/api/compute/v1alpha1/machine_types.go +++ b/api/compute/v1alpha1/machine_types.go @@ -189,6 +189,15 @@ const ( // MachineConditionType is a type a MachineCondition can have. type MachineConditionType string +const ( + // MachineConditionReady indicates that the machine is ready. + MachineConditionReady MachineConditionType = "MachineReady" + // MachineConditionVolumesReady indicates that the volumes are ready. + MachineConditionVolumesReady MachineConditionType = "VolumesReady" + // MachineConditionNetworkInterfacesReady indicates that the network interfaces are ready. + MachineConditionNetworkInterfacesReady MachineConditionType = "NetworkInterfacesReady" +) + // MachineCondition is one of the conditions of a machine. type MachineCondition struct { // Type is the type of the condition. diff --git a/poollet/machinepoollet/controllers/machine_controller.go b/poollet/machinepoollet/controllers/machine_controller.go index 7d5aa39ea..8d5044e22 100644 --- a/poollet/machinepoollet/controllers/machine_controller.go +++ b/poollet/machinepoollet/controllers/machine_controller.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "sort" "strconv" "github.com/go-logr/logr" @@ -552,7 +553,7 @@ func (r *MachineReconciler) computeMachineReadyCondition(state computev1alpha1.M } return computev1alpha1.MachineCondition{ - Type: "MachineReady", + Type: computev1alpha1.MachineConditionReady, Status: status, Reason: reason, Message: message, @@ -585,9 +586,8 @@ func (r *MachineReconciler) computeVolumesReadyCondition(volumeStatuses []comput if status == corev1.ConditionTrue { for _, vs := range volumeStatuses { - if vs.LastStateTransitionTime != nil { + if vs.LastStateTransitionTime != nil && (lastTransitionTime == nil || vs.LastStateTransitionTime.After(lastTransitionTime.Time)) { lastTransitionTime = vs.LastStateTransitionTime - break } } } @@ -598,7 +598,7 @@ func (r *MachineReconciler) computeVolumesReadyCondition(volumeStatuses []comput } return computev1alpha1.MachineCondition{ - Type: computev1alpha1.MachineConditionType("VolumesReady"), + Type: computev1alpha1.MachineConditionVolumesReady, Status: status, Reason: reason, Message: message, @@ -627,9 +627,8 @@ func (r *MachineReconciler) computeNetworkInterfacesReadyCondition(nicStatuses [ if status == corev1.ConditionTrue { for _, nicStatus := range nicStatuses { - if nicStatus.LastStateTransitionTime != nil { + if nicStatus.LastStateTransitionTime != nil && (lastTransitionTime == nil || nicStatus.LastStateTransitionTime.After(lastTransitionTime.Time)) { lastTransitionTime = nicStatus.LastStateTransitionTime - break } } } @@ -640,7 +639,7 @@ func (r *MachineReconciler) computeNetworkInterfacesReadyCondition(nicStatuses [ } return computev1alpha1.MachineCondition{ - Type: computev1alpha1.MachineConditionType("NetworkInterfacesReady"), + Type: computev1alpha1.MachineConditionNetworkInterfacesReady, Status: status, Reason: reason, Message: message, @@ -667,7 +666,33 @@ func (r *MachineReconciler) mergeMachineConditions( } } - return updated + // Group conditions by type and keep only the latest 10 per type (by LastTransitionTime) + const maxConditionsPerType = 10 + conditionsByType := make(map[string][]computev1alpha1.MachineCondition) + for _, cond := range updated { + condType := string(cond.Type) + conditionsByType[condType] = append(conditionsByType[condType], cond) + } + + var result []computev1alpha1.MachineCondition + // Sort condition types for deterministic ordering + types := make([]string, 0, len(conditionsByType)) + for t := range conditionsByType { + types = append(types, t) + } + sort.Strings(types) + for _, t := range types { + conds := conditionsByType[t] + sort.Slice(conds, func(i, j int) bool { + return conds[i].LastTransitionTime.Before(&conds[j].LastTransitionTime) + }) + if len(conds) > maxConditionsPerType { + conds = conds[len(conds)-maxConditionsPerType:] + } + result = append(result, conds...) + } + + return result } func (r *MachineReconciler) prepareIRIPower(power computev1alpha1.Power) (iri.Power, error) { @@ -1019,7 +1044,7 @@ func (r *MachineReconciler) enqueueMachinesReferencingNetworkInterface() handler }, r.matchingWatchLabel(), ); err != nil { - log.Error(err, "Error listing machines using network interface", "NetworkInterfaceKey", client.ObjectKeyFromObject(nic)) + log.Error(err, "Error listing machines using secret", "NetworkInterfaceKey", client.ObjectKeyFromObject(nic)) return nil }