Add observedGeneration and validation to pod status and conditions

This commit is contained in:
Natasha Sarkar 2025-02-21 18:46:17 +00:00
parent d3548f487d
commit eab9197d1a
8 changed files with 79 additions and 8 deletions

View File

@ -2955,8 +2955,11 @@ const (
// PodCondition represents pod's condition
type PodCondition struct {
Type PodConditionType
Status ConditionStatus
Type PodConditionType
// +featureGate=PodObservedGenerationTracking
// +optional
ObservedGeneration int64
Status ConditionStatus
// +optional
LastProbeTime metav1.Time
// +optional
@ -4164,6 +4167,11 @@ type EphemeralContainer struct {
// PodStatus represents information about the status of a pod. Status may trail the actual
// state of a system.
type PodStatus struct {
// If set, this represents the .metadata.generation that the pod status was set based upon.
// This is an alpha field. Enable PodObservedGenerationTracking to be able to use this field.
// +featureGate=PodObservedGenerationTracking
// +optional
ObservedGeneration int64
// +optional
Phase PodPhase
// +optional

View File

@ -5449,9 +5449,10 @@ func ValidatePodStatusUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions
fldPath := field.NewPath("metadata")
allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath)
allErrs = append(allErrs, ValidatePodSpecificAnnotationUpdates(newPod, oldPod, fldPath.Child("annotations"), opts)...)
allErrs = append(allErrs, validatePodConditions(newPod.Status.Conditions, fldPath.Child("conditions"))...)
fldPath = field.NewPath("status")
allErrs = append(allErrs, validatePodConditions(newPod.Status.Conditions, fldPath.Child("conditions"))...)
if newPod.Spec.NodeName != oldPod.Spec.NodeName {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("nodeName"), "may not be changed directly"))
}
@ -5462,6 +5463,10 @@ func ValidatePodStatusUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions
}
}
if newPod.Status.ObservedGeneration < 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("observedGeneration"), newPod.Status.ObservedGeneration, "must be a non-negative integer"))
}
// Pod QoS is immutable
allErrs = append(allErrs, ValidateImmutableField(newPod.Status.QOSClass, oldPod.Status.QOSClass, fldPath.Child("qosClass"))...)
@ -5497,7 +5502,8 @@ func ValidatePodStatusUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions
return allErrs
}
// validatePodConditions tests if the custom pod conditions are valid.
// validatePodConditions tests if the custom pod conditions are valid, and that the observedGeneration
// is a non-negative integer.
func validatePodConditions(conditions []core.PodCondition, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
systemConditions := sets.New(
@ -5505,6 +5511,9 @@ func validatePodConditions(conditions []core.PodCondition, fldPath *field.Path)
core.PodReady,
core.PodInitialized)
for i, condition := range conditions {
if condition.ObservedGeneration < 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("observedGeneration"), condition.ObservedGeneration, "must be a non-negative integer"))
}
if systemConditions.Has(condition.Type) {
continue
}

View File

@ -13897,6 +13897,50 @@ func TestValidatePodStatusUpdate(t *testing.T) {
err string
test string
}{{
*podtest.MakePod("foo",
podtest.SetStatus(core.PodStatus{
ObservedGeneration: 1,
}),
),
*podtest.MakePod("foo"),
"",
"set valid status.observedGeneration",
}, {
*podtest.MakePod("foo",
podtest.SetStatus(core.PodStatus{
Conditions: []core.PodCondition{{
Type: core.PodScheduled,
Status: core.ConditionTrue,
ObservedGeneration: 1,
}},
}),
),
*podtest.MakePod("foo"),
"",
"set valid condition.observedGeneration",
}, {
*podtest.MakePod("foo",
podtest.SetStatus(core.PodStatus{
ObservedGeneration: -1,
}),
),
*podtest.MakePod("foo"),
"status.observedGeneration: Invalid value: -1: must be a non-negative integer",
"set invalid status.observedGeneration",
}, {
*podtest.MakePod("foo",
podtest.SetStatus(core.PodStatus{
Conditions: []core.PodCondition{{
Type: core.PodScheduled,
Status: core.ConditionTrue,
ObservedGeneration: -1,
}},
}),
),
*podtest.MakePod("foo"),
"status.conditions[0].observedGeneration: Invalid value: -1: must be a non-negative integer",
"set invalid condition.observedGeneration",
}, {
*podtest.MakePod("foo",
podtest.SetNodeName("node1"),
podtest.SetStatus(core.PodStatus{

View File

@ -3308,6 +3308,11 @@ type PodCondition struct {
// Type is the type of the condition.
// More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions
Type PodConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=PodConditionType"`
// If set, this represents the .metadata.generation that the pod condition was set based upon.
// This is an alpha field. Enable PodObservedGenerationTracking to be able to use this field.
// +featureGate=PodObservedGenerationTracking
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// Status is the status of the condition.
// Can be True, False, Unknown.
// More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions
@ -4841,6 +4846,11 @@ type EphemeralContainer struct {
// state of a system, especially if the node that hosts the pod cannot contact the control
// plane.
type PodStatus struct {
// If set, this represents the .metadata.generation that the pod status was set based upon.
// This is an alpha field. Enable PodObservedGenerationTracking to be able to use this field.
// +featureGate=PodObservedGenerationTracking
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// The phase of a Pod is a simple, high-level summary of where the Pod is in its lifecycle.
// The conditions array, the reason and message fields, and the individual container status
// arrays contain more detail about the pod's status.

View File

@ -172,7 +172,7 @@ func LogPodStates(pods []v1.Pod) {
if pod.DeletionGracePeriodSeconds != nil {
grace = fmt.Sprintf("%ds", *pod.DeletionGracePeriodSeconds)
}
framework.Logf("%-[1]*[2]s %-[3]*[4]s %-[5]*[6]s %-[7]*[8]s %[9]s",
framework.Logf("%-[1]*[2]s %-[3]*[4]s %-[5]*[6]s %-[7]*[8]s %[9]v",
maxPodW, pod.ObjectMeta.Name, maxNodeW, pod.Spec.NodeName, maxPhaseW, pod.Status.Phase, maxGraceW, grace, pod.Status.Conditions)
}
framework.Logf("") // Final empty line helps for readability.

View File

@ -313,7 +313,7 @@ func WatchPods(ctx context.Context, cs clientset.Interface, ns string, to io.Wri
}
buffer := new(bytes.Buffer)
fmt.Fprintf(buffer,
"%s pod: %s: %s/%s %s: %s %s\n",
"%s pod: %s: %s/%s %s: %s %v\n",
time.Now().Format(timeFormat),
e.Type,
pod.Namespace,

View File

@ -221,7 +221,7 @@ var _ = sigDescribe(feature.Windows, "GracefulNodeShutdown", framework.WithSeria
if !isPodReadyToStartConditionSetToFalse(&pod) {
framework.Logf("Expecting pod (%v/%v) 's ready to start condition set to false, "+
"but it's not currently: Pod Condition %+v", pod.Namespace, pod.Name, pod.Status.Conditions)
return fmt.Errorf("pod (%v/%v) 's ready to start condition should be false, condition: %s, phase: %s",
return fmt.Errorf("pod (%v/%v) 's ready to start condition should be false, condition: %v, phase: %s",
pod.Namespace, pod.Name, pod.Status.Conditions, pod.Status.Phase)
}
}

View File

@ -341,7 +341,7 @@ var _ = SIGDescribe("GracefulNodeShutdown", framework.WithSerial(), feature.Grac
if !isPodReadyToStartConditionSetToFalse(&pod) {
framework.Logf("Expecting pod (%v/%v) 's ready to start condition set to false, "+
"but it's not currently: Pod Condition %+v", pod.Namespace, pod.Name, pod.Status.Conditions)
return fmt.Errorf("pod (%v/%v) 's ready to start condition should be false, condition: %s, phase: %s",
return fmt.Errorf("pod (%v/%v) 's ready to start condition should be false, condition: %v, phase: %s",
pod.Namespace, pod.Name, pod.Status.Conditions, pod.Status.Phase)
}
}