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

@ -2956,6 +2956,9 @@ const (
// PodCondition represents pod's condition // PodCondition represents pod's condition
type PodCondition struct { type PodCondition struct {
Type PodConditionType Type PodConditionType
// +featureGate=PodObservedGenerationTracking
// +optional
ObservedGeneration int64
Status ConditionStatus Status ConditionStatus
// +optional // +optional
LastProbeTime metav1.Time LastProbeTime metav1.Time
@ -4164,6 +4167,11 @@ type EphemeralContainer struct {
// PodStatus represents information about the status of a pod. Status may trail the actual // PodStatus represents information about the status of a pod. Status may trail the actual
// state of a system. // state of a system.
type PodStatus struct { 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 // +optional
Phase PodPhase Phase PodPhase
// +optional // +optional

View File

@ -5449,9 +5449,10 @@ func ValidatePodStatusUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions
fldPath := field.NewPath("metadata") fldPath := field.NewPath("metadata")
allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath) allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath)
allErrs = append(allErrs, ValidatePodSpecificAnnotationUpdates(newPod, oldPod, fldPath.Child("annotations"), opts)...) allErrs = append(allErrs, ValidatePodSpecificAnnotationUpdates(newPod, oldPod, fldPath.Child("annotations"), opts)...)
allErrs = append(allErrs, validatePodConditions(newPod.Status.Conditions, fldPath.Child("conditions"))...)
fldPath = field.NewPath("status") fldPath = field.NewPath("status")
allErrs = append(allErrs, validatePodConditions(newPod.Status.Conditions, fldPath.Child("conditions"))...)
if newPod.Spec.NodeName != oldPod.Spec.NodeName { if newPod.Spec.NodeName != oldPod.Spec.NodeName {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("nodeName"), "may not be changed directly")) 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 // Pod QoS is immutable
allErrs = append(allErrs, ValidateImmutableField(newPod.Status.QOSClass, oldPod.Status.QOSClass, fldPath.Child("qosClass"))...) 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 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 { func validatePodConditions(conditions []core.PodCondition, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
systemConditions := sets.New( systemConditions := sets.New(
@ -5505,6 +5511,9 @@ func validatePodConditions(conditions []core.PodCondition, fldPath *field.Path)
core.PodReady, core.PodReady,
core.PodInitialized) core.PodInitialized)
for i, condition := range conditions { 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) { if systemConditions.Has(condition.Type) {
continue continue
} }

View File

@ -13897,6 +13897,50 @@ func TestValidatePodStatusUpdate(t *testing.T) {
err string err string
test 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.MakePod("foo",
podtest.SetNodeName("node1"), podtest.SetNodeName("node1"),
podtest.SetStatus(core.PodStatus{ podtest.SetStatus(core.PodStatus{

View File

@ -3308,6 +3308,11 @@ type PodCondition struct {
// Type is the type of the condition. // Type is the type of the condition.
// More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions // 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"` 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. // Status is the status of the condition.
// Can be True, False, Unknown. // Can be True, False, Unknown.
// More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions // 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 // state of a system, especially if the node that hosts the pod cannot contact the control
// plane. // plane.
type PodStatus struct { 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 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 // The conditions array, the reason and message fields, and the individual container status
// arrays contain more detail about the pod's 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 { if pod.DeletionGracePeriodSeconds != nil {
grace = fmt.Sprintf("%ds", *pod.DeletionGracePeriodSeconds) 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) 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. 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) buffer := new(bytes.Buffer)
fmt.Fprintf(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), time.Now().Format(timeFormat),
e.Type, e.Type,
pod.Namespace, pod.Namespace,

View File

@ -221,7 +221,7 @@ var _ = sigDescribe(feature.Windows, "GracefulNodeShutdown", framework.WithSeria
if !isPodReadyToStartConditionSetToFalse(&pod) { if !isPodReadyToStartConditionSetToFalse(&pod) {
framework.Logf("Expecting pod (%v/%v) 's ready to start condition set to false, "+ 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) "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) 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) { if !isPodReadyToStartConditionSetToFalse(&pod) {
framework.Logf("Expecting pod (%v/%v) 's ready to start condition set to false, "+ 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) "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) pod.Namespace, pod.Name, pod.Status.Conditions, pod.Status.Phase)
} }
} }