mirror of
https://github.com/optim-enterprises-bv/kubernetes.git
synced 2025-12-16 21:07:22 +00:00
Merge pull request #64057 from freehan/pod-ready-api
Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Pod Ready++ API **What this PR does / why we need it**: Implements API changes for [pod ready++](https://github.com/kubernetes/community/blob/master/keps/sig-network/0007-pod-ready%2B%2B.md) ```release-note NONE ``` /assign @MrHohn for review /assign @thockin for approval
This commit is contained in:
@@ -2409,6 +2409,12 @@ const (
|
||||
TolerationOpEqual TolerationOperator = "Equal"
|
||||
)
|
||||
|
||||
// PodReadinessGate contains the reference to a pod condition
|
||||
type PodReadinessGate struct {
|
||||
// ConditionType refers to a condition in the pod's condition list with matching type.
|
||||
ConditionType PodConditionType
|
||||
}
|
||||
|
||||
// PodSpec is a description of a pod
|
||||
type PodSpec struct {
|
||||
Volumes []Volume
|
||||
@@ -2505,6 +2511,12 @@ type PodSpec struct {
|
||||
// configuration based on DNSPolicy.
|
||||
// +optional
|
||||
DNSConfig *PodDNSConfig
|
||||
// If specified, all readiness gates will be evaluated for pod readiness.
|
||||
// A pod is ready when all its containers are ready AND
|
||||
// all conditions specified in the readiness gates have status equal to "True"
|
||||
// More info: https://github.com/kubernetes/community/blob/master/keps/sig-network/0007-pod-ready%2B%2B.md
|
||||
// +optional
|
||||
ReadinessGates []PodReadinessGate
|
||||
}
|
||||
|
||||
// HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the
|
||||
|
||||
24
pkg/apis/core/v1/zz_generated.conversion.go
generated
24
pkg/apis/core/v1/zz_generated.conversion.go
generated
@@ -286,6 +286,8 @@ func RegisterConversions(scheme *runtime.Scheme) error {
|
||||
Convert_core_PodPortForwardOptions_To_v1_PodPortForwardOptions,
|
||||
Convert_v1_PodProxyOptions_To_core_PodProxyOptions,
|
||||
Convert_core_PodProxyOptions_To_v1_PodProxyOptions,
|
||||
Convert_v1_PodReadinessGate_To_core_PodReadinessGate,
|
||||
Convert_core_PodReadinessGate_To_v1_PodReadinessGate,
|
||||
Convert_v1_PodSecurityContext_To_core_PodSecurityContext,
|
||||
Convert_core_PodSecurityContext_To_v1_PodSecurityContext,
|
||||
Convert_v1_PodSignature_To_core_PodSignature,
|
||||
@@ -3715,6 +3717,26 @@ func Convert_core_PodProxyOptions_To_v1_PodProxyOptions(in *core.PodProxyOptions
|
||||
return autoConvert_core_PodProxyOptions_To_v1_PodProxyOptions(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_PodReadinessGate_To_core_PodReadinessGate(in *v1.PodReadinessGate, out *core.PodReadinessGate, s conversion.Scope) error {
|
||||
out.ConditionType = core.PodConditionType(in.ConditionType)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_PodReadinessGate_To_core_PodReadinessGate is an autogenerated conversion function.
|
||||
func Convert_v1_PodReadinessGate_To_core_PodReadinessGate(in *v1.PodReadinessGate, out *core.PodReadinessGate, s conversion.Scope) error {
|
||||
return autoConvert_v1_PodReadinessGate_To_core_PodReadinessGate(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_core_PodReadinessGate_To_v1_PodReadinessGate(in *core.PodReadinessGate, out *v1.PodReadinessGate, s conversion.Scope) error {
|
||||
out.ConditionType = v1.PodConditionType(in.ConditionType)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_core_PodReadinessGate_To_v1_PodReadinessGate is an autogenerated conversion function.
|
||||
func Convert_core_PodReadinessGate_To_v1_PodReadinessGate(in *core.PodReadinessGate, out *v1.PodReadinessGate, s conversion.Scope) error {
|
||||
return autoConvert_core_PodReadinessGate_To_v1_PodReadinessGate(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_PodSecurityContext_To_core_PodSecurityContext(in *v1.PodSecurityContext, out *core.PodSecurityContext, s conversion.Scope) error {
|
||||
out.SELinuxOptions = (*core.SELinuxOptions)(unsafe.Pointer(in.SELinuxOptions))
|
||||
out.RunAsUser = (*int64)(unsafe.Pointer(in.RunAsUser))
|
||||
@@ -3825,6 +3847,7 @@ func autoConvert_v1_PodSpec_To_core_PodSpec(in *v1.PodSpec, out *core.PodSpec, s
|
||||
out.PriorityClassName = in.PriorityClassName
|
||||
out.Priority = (*int32)(unsafe.Pointer(in.Priority))
|
||||
out.DNSConfig = (*core.PodDNSConfig)(unsafe.Pointer(in.DNSConfig))
|
||||
out.ReadinessGates = *(*[]core.PodReadinessGate)(unsafe.Pointer(&in.ReadinessGates))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3889,6 +3912,7 @@ func autoConvert_core_PodSpec_To_v1_PodSpec(in *core.PodSpec, out *v1.PodSpec, s
|
||||
out.PriorityClassName = in.PriorityClassName
|
||||
out.Priority = (*int32)(unsafe.Pointer(in.Priority))
|
||||
out.DNSConfig = (*v1.PodDNSConfig)(unsafe.Pointer(in.DNSConfig))
|
||||
out.ReadinessGates = *(*[]v1.PodReadinessGate)(unsafe.Pointer(&in.ReadinessGates))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2647,6 +2647,19 @@ const (
|
||||
MaxDNSSearchListChars = 256
|
||||
)
|
||||
|
||||
func validateReadinessGates(readinessGates []core.PodReadinessGate, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.PodReadinessGates) && len(readinessGates) > 0 {
|
||||
return append(allErrs, field.Forbidden(fldPath, "PodReadinessGates is disabled by feature gate"))
|
||||
}
|
||||
for i, value := range readinessGates {
|
||||
for _, msg := range validation.IsQualifiedName(string(value.ConditionType)) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("conditionType"), string(value.ConditionType), msg))
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validatePodDNSConfig(dnsConfig *core.PodDNSConfig, dnsPolicy *core.DNSPolicy, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
@@ -2935,6 +2948,7 @@ func ValidatePodSpec(spec *core.PodSpec, fldPath *field.Path) field.ErrorList {
|
||||
allErrs = append(allErrs, validateImagePullSecrets(spec.ImagePullSecrets, fldPath.Child("imagePullSecrets"))...)
|
||||
allErrs = append(allErrs, validateAffinity(spec.Affinity, fldPath.Child("affinity"))...)
|
||||
allErrs = append(allErrs, validatePodDNSConfig(spec.DNSConfig, &spec.DNSPolicy, fldPath.Child("dnsConfig"))...)
|
||||
allErrs = append(allErrs, validateReadinessGates(spec.ReadinessGates, fldPath.Child("readinessGates"))...)
|
||||
if len(spec.ServiceAccountName) > 0 {
|
||||
for _, msg := range ValidateServiceAccountName(spec.ServiceAccountName, false) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("serviceAccountName"), spec.ServiceAccountName, msg))
|
||||
@@ -3485,6 +3499,7 @@ func ValidatePodStatusUpdate(newPod, oldPod *core.Pod) field.ErrorList {
|
||||
fldPath := field.NewPath("metadata")
|
||||
allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath)
|
||||
allErrs = append(allErrs, ValidatePodSpecificAnnotationUpdates(newPod, oldPod, fldPath.Child("annotations"))...)
|
||||
allErrs = append(allErrs, validatePodConditions(newPod.Status.Conditions, fldPath.Child("conditions"))...)
|
||||
|
||||
fldPath = field.NewPath("status")
|
||||
if newPod.Spec.NodeName != oldPod.Spec.NodeName {
|
||||
@@ -3508,6 +3523,21 @@ func ValidatePodStatusUpdate(newPod, oldPod *core.Pod) field.ErrorList {
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validatePodConditions tests if the custom pod conditions are valid.
|
||||
func validatePodConditions(conditions []core.PodCondition, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
systemConditions := sets.NewString(string(core.PodScheduled), string(core.PodReady), string(core.PodInitialized))
|
||||
for i, condition := range conditions {
|
||||
if systemConditions.Has(string(condition.Type)) {
|
||||
continue
|
||||
}
|
||||
for _, msg := range validation.IsQualifiedName(string(condition.Type)) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("Type"), string(condition.Type), msg))
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidatePodBinding tests if required fields in the pod binding are legal.
|
||||
func ValidatePodBinding(binding *core.Binding) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
@@ -5865,6 +5865,149 @@ func TestValidatePodDNSConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePodReadinessGates(t *testing.T) {
|
||||
podReadinessGatesEnabled := utilfeature.DefaultFeatureGate.Enabled(features.PodReadinessGates)
|
||||
defer func() {
|
||||
// Restoring the old value.
|
||||
if err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=%v", features.PodReadinessGates, podReadinessGatesEnabled)); err != nil {
|
||||
t.Errorf("Failed to restore PodReadinessGates feature gate: %v", err)
|
||||
}
|
||||
}()
|
||||
if err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=true", features.PodReadinessGates)); err != nil {
|
||||
t.Errorf("Failed to enable PodReadinessGates feature gate: %v", err)
|
||||
}
|
||||
|
||||
successCases := []struct {
|
||||
desc string
|
||||
readinessGates []core.PodReadinessGate
|
||||
}{
|
||||
{
|
||||
"no gate",
|
||||
[]core.PodReadinessGate{},
|
||||
},
|
||||
{
|
||||
"one readiness gate",
|
||||
[]core.PodReadinessGate{
|
||||
{
|
||||
ConditionType: core.PodConditionType("example.com/condition"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"two readiness gates",
|
||||
[]core.PodReadinessGate{
|
||||
{
|
||||
ConditionType: core.PodConditionType("example.com/condition1"),
|
||||
},
|
||||
{
|
||||
ConditionType: core.PodConditionType("example.com/condition2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range successCases {
|
||||
if errs := validateReadinessGates(tc.readinessGates, field.NewPath("field")); len(errs) != 0 {
|
||||
t.Errorf("expect tc %q to success: %v", tc.desc, errs)
|
||||
}
|
||||
}
|
||||
|
||||
errorCases := []struct {
|
||||
desc string
|
||||
readinessGates []core.PodReadinessGate
|
||||
}{
|
||||
{
|
||||
"invalid condition type",
|
||||
[]core.PodReadinessGate{
|
||||
{
|
||||
ConditionType: core.PodConditionType("invalid/condition/type"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range errorCases {
|
||||
if errs := validateReadinessGates(tc.readinessGates, field.NewPath("field")); len(errs) == 0 {
|
||||
t.Errorf("expected tc %q to fail", tc.desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePodConditions(t *testing.T) {
|
||||
successCases := []struct {
|
||||
desc string
|
||||
podConditions []core.PodCondition
|
||||
}{
|
||||
{
|
||||
"no condition",
|
||||
[]core.PodCondition{},
|
||||
},
|
||||
{
|
||||
"one system condition",
|
||||
[]core.PodCondition{
|
||||
{
|
||||
Type: core.PodReady,
|
||||
Status: core.ConditionTrue,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"one system condition and one custom condition",
|
||||
[]core.PodCondition{
|
||||
{
|
||||
Type: core.PodReady,
|
||||
Status: core.ConditionTrue,
|
||||
},
|
||||
{
|
||||
Type: core.PodConditionType("example.com/condition"),
|
||||
Status: core.ConditionFalse,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"two custom condition",
|
||||
[]core.PodCondition{
|
||||
{
|
||||
Type: core.PodConditionType("foobar"),
|
||||
Status: core.ConditionTrue,
|
||||
},
|
||||
{
|
||||
Type: core.PodConditionType("example.com/condition"),
|
||||
Status: core.ConditionFalse,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range successCases {
|
||||
if errs := validatePodConditions(tc.podConditions, field.NewPath("field")); len(errs) != 0 {
|
||||
t.Errorf("expected tc %q to success, but got: %v", tc.desc, errs)
|
||||
}
|
||||
}
|
||||
|
||||
errorCases := []struct {
|
||||
desc string
|
||||
podConditions []core.PodCondition
|
||||
}{
|
||||
{
|
||||
"one system condition and a invalid custom condition",
|
||||
[]core.PodCondition{
|
||||
{
|
||||
Type: core.PodReady,
|
||||
Status: core.ConditionStatus("True"),
|
||||
},
|
||||
{
|
||||
Type: core.PodConditionType("invalid/custom/condition"),
|
||||
Status: core.ConditionStatus("True"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range errorCases {
|
||||
if errs := validatePodConditions(tc.podConditions, field.NewPath("field")); len(errs) == 0 {
|
||||
t.Errorf("expected tc %q to fail", tc.desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePodSpec(t *testing.T) {
|
||||
activeDeadlineSeconds := int64(30)
|
||||
activeDeadlineSecondsMax := int64(math.MaxInt32)
|
||||
|
||||
21
pkg/apis/core/zz_generated.deepcopy.go
generated
21
pkg/apis/core/zz_generated.deepcopy.go
generated
@@ -3719,6 +3719,22 @@ func (in *PodProxyOptions) DeepCopyObject() runtime.Object {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PodReadinessGate) DeepCopyInto(out *PodReadinessGate) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodReadinessGate.
|
||||
func (in *PodReadinessGate) DeepCopy() *PodReadinessGate {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PodReadinessGate)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PodSecurityContext) DeepCopyInto(out *PodSecurityContext) {
|
||||
*out = *in
|
||||
@@ -3932,6 +3948,11 @@ func (in *PodSpec) DeepCopyInto(out *PodSpec) {
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
if in.ReadinessGates != nil {
|
||||
in, out := &in.ReadinessGates, &out.ReadinessGates
|
||||
*out = make([]PodReadinessGate, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -292,6 +292,12 @@ const (
|
||||
// while making decisions.
|
||||
BalanceAttachedNodeVolumes utilfeature.Feature = "BalanceAttachedNodeVolumes"
|
||||
|
||||
// owner @freehan
|
||||
// beta: v1.11
|
||||
//
|
||||
// Support Pod Ready++
|
||||
PodReadinessGates utilfeature.Feature = "PodReadinessGates"
|
||||
|
||||
// owner: @lichuqiang
|
||||
// alpha: v1.11
|
||||
//
|
||||
@@ -362,6 +368,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
|
||||
VolumeSubpath: {Default: true, PreRelease: utilfeature.GA},
|
||||
BalanceAttachedNodeVolumes: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
DynamicProvisioningScheduling: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
PodReadinessGates: {Default: false, PreRelease: utilfeature.Beta},
|
||||
VolumeSubpathEnvExpansion: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
KubeletPluginsWatcher: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user