mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 11:21:47 +00:00
APIs, Validation and condition enforcements
- New API field .spec.schedulingGates - Validation and drop disabled fields - Disallow binding a Pod carrying non-nil schedulingGates - Disallow creating a Pod with non-nil nodeName and non-nil schedulingGates - Adds a {type:PodScheduled, reason:WaitingForGates} condition if necessary - New literal SchedulingGated in the STATUS column of `k get pod`
This commit is contained in:
parent
d62cc3dc6d
commit
7b6293b6b6
@ -537,6 +537,11 @@ func dropDisabledFields(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the feature is disabled and not in use, drop the schedulingGates field.
|
||||||
|
if !utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness) && !schedulingGatesInUse(oldPodSpec) {
|
||||||
|
podSpec.SchedulingGates = nil
|
||||||
|
}
|
||||||
|
|
||||||
dropDisabledProcMountField(podSpec, oldPodSpec)
|
dropDisabledProcMountField(podSpec, oldPodSpec)
|
||||||
|
|
||||||
dropDisabledTopologySpreadConstraintsFields(podSpec, oldPodSpec)
|
dropDisabledTopologySpreadConstraintsFields(podSpec, oldPodSpec)
|
||||||
@ -719,6 +724,14 @@ func probeGracePeriodInUse(podSpec *api.PodSpec) bool {
|
|||||||
return inUse
|
return inUse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// schedulingGatesInUse returns true if the pod spec is non-nil and it has SchedulingGates field set.
|
||||||
|
func schedulingGatesInUse(podSpec *api.PodSpec) bool {
|
||||||
|
if podSpec == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return len(podSpec.SchedulingGates) != 0
|
||||||
|
}
|
||||||
|
|
||||||
// SeccompAnnotationForField takes a pod seccomp profile field and returns the
|
// SeccompAnnotationForField takes a pod seccomp profile field and returns the
|
||||||
// converted annotation value
|
// converted annotation value
|
||||||
func SeccompAnnotationForField(field *api.SeccompProfile) string {
|
func SeccompAnnotationForField(field *api.SeccompProfile) string {
|
||||||
|
@ -1935,3 +1935,85 @@ func TestDropHostUsers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDropSchedulingGates(t *testing.T) {
|
||||||
|
podWithSchedulingGates := func() *api.Pod {
|
||||||
|
return &api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
SchedulingGates: []api.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
{Name: "bar"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
podWithoutSchedulingGates := func() *api.Pod { return &api.Pod{} }
|
||||||
|
|
||||||
|
podInfo := []struct {
|
||||||
|
description string
|
||||||
|
hasSchedulingGatesField bool
|
||||||
|
pod func() *api.Pod
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "has SchedulingGates field",
|
||||||
|
hasSchedulingGatesField: true,
|
||||||
|
pod: podWithSchedulingGates,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "does not have SchedulingGates field",
|
||||||
|
hasSchedulingGatesField: false,
|
||||||
|
pod: podWithoutSchedulingGates,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "is nil",
|
||||||
|
hasSchedulingGatesField: false,
|
||||||
|
pod: func() *api.Pod { return nil },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, enabled := range []bool{true, false} {
|
||||||
|
for _, oldPodInfo := range podInfo {
|
||||||
|
for _, newPodInfo := range podInfo {
|
||||||
|
oldPodHasSchedulingGates, oldPod := oldPodInfo.hasSchedulingGatesField, oldPodInfo.pod()
|
||||||
|
newPodHasSchedulingGates, newPod := newPodInfo.hasSchedulingGatesField, newPodInfo.pod()
|
||||||
|
if newPod == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(fmt.Sprintf("feature enabled=%v, old pod %v, new pod %v", enabled, oldPodInfo.description, newPodInfo.description), func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, enabled)()
|
||||||
|
var oldPodSpec *api.PodSpec
|
||||||
|
if oldPod != nil {
|
||||||
|
oldPodSpec = &oldPod.Spec
|
||||||
|
}
|
||||||
|
dropDisabledFields(&newPod.Spec, nil, oldPodSpec, nil)
|
||||||
|
// Old Pod should never be changed.
|
||||||
|
if diff := cmp.Diff(oldPod, oldPodInfo.pod()); diff != "" {
|
||||||
|
t.Errorf("old pod changed: %v", diff)
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case enabled || oldPodHasSchedulingGates:
|
||||||
|
// New Pod should not be changed if the feature is enabled, or if the old Pod had schedulingGates.
|
||||||
|
if diff := cmp.Diff(newPod, newPodInfo.pod()); diff != "" {
|
||||||
|
t.Errorf("new pod changed: %v", diff)
|
||||||
|
}
|
||||||
|
case newPodHasSchedulingGates:
|
||||||
|
// New Pod should be changed.
|
||||||
|
if reflect.DeepEqual(newPod, newPodInfo.pod()) {
|
||||||
|
t.Errorf("new pod was not changed")
|
||||||
|
}
|
||||||
|
// New Pod should not have SchedulingGates field.
|
||||||
|
if diff := cmp.Diff(newPod, podWithoutSchedulingGates()); diff != "" {
|
||||||
|
t.Errorf("new pod has SchedulingGates field: %v", diff)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// New pod should not need to be changed.
|
||||||
|
if diff := cmp.Diff(newPod, newPodInfo.pod()); diff != "" {
|
||||||
|
t.Errorf("new pod changed: %v", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2428,6 +2428,9 @@ const (
|
|||||||
// PodReasonUnschedulable reason in PodScheduled PodCondition means that the scheduler
|
// PodReasonUnschedulable reason in PodScheduled PodCondition means that the scheduler
|
||||||
// can't schedule the pod right now, for example due to insufficient resources in the cluster.
|
// can't schedule the pod right now, for example due to insufficient resources in the cluster.
|
||||||
PodReasonUnschedulable = "Unschedulable"
|
PodReasonUnschedulable = "Unschedulable"
|
||||||
|
// PodReasonSchedulingGated reason in PodScheduled PodCondition means that the scheduler
|
||||||
|
// skips scheduling the pod because one or more scheduling gates are still present.
|
||||||
|
PodReasonSchedulingGated = "SchedulingGated"
|
||||||
// ContainersReady indicates whether all containers in the pod are ready.
|
// ContainersReady indicates whether all containers in the pod are ready.
|
||||||
ContainersReady PodConditionType = "ContainersReady"
|
ContainersReady PodConditionType = "ContainersReady"
|
||||||
// AlphaNoCompatGuaranteeDisruptionTarget indicates the pod is about to be deleted due to a
|
// AlphaNoCompatGuaranteeDisruptionTarget indicates the pod is about to be deleted due to a
|
||||||
@ -2997,6 +3000,12 @@ type PodSpec struct {
|
|||||||
// - spec.containers[*].securityContext.runAsGroup
|
// - spec.containers[*].securityContext.runAsGroup
|
||||||
// +optional
|
// +optional
|
||||||
OS *PodOS
|
OS *PodOS
|
||||||
|
// SchedulingGates is an opaque list of values that if specified will block scheduling the pod.
|
||||||
|
// More info: https://git.k8s.io/enhancements/keps/sig-scheduling/3521-pod-scheduling-readiness.
|
||||||
|
//
|
||||||
|
// This is an alpha-level feature enabled by PodSchedulingReadiness feature gate.
|
||||||
|
// +optional
|
||||||
|
SchedulingGates []PodSchedulingGate
|
||||||
}
|
}
|
||||||
|
|
||||||
// OSName is the set of OS'es that can be used in OS.
|
// OSName is the set of OS'es that can be used in OS.
|
||||||
@ -3017,6 +3026,13 @@ type PodOS struct {
|
|||||||
Name OSName
|
Name OSName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PodSchedulingGate is associated to a Pod to guard its scheduling.
|
||||||
|
type PodSchedulingGate struct {
|
||||||
|
// Name of the scheduling gate.
|
||||||
|
// Each scheduling gate must have a unique name field.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
// HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the
|
// HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the
|
||||||
// pod's hosts file.
|
// pod's hosts file.
|
||||||
type HostAlias struct {
|
type HostAlias struct {
|
||||||
|
@ -3272,6 +3272,22 @@ func validateReadinessGates(readinessGates []core.PodReadinessGate, fldPath *fie
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateSchedulingGates(schedulingGates []core.PodSchedulingGate, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
// There should be no duplicates in the list of scheduling gates.
|
||||||
|
seen := sets.String{}
|
||||||
|
for i, schedulingGate := range schedulingGates {
|
||||||
|
if schedulingGate.Name == "" {
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath.Index(i), "must not be empty"))
|
||||||
|
}
|
||||||
|
if seen.Has(schedulingGate.Name) {
|
||||||
|
allErrs = append(allErrs, field.Duplicate(fldPath.Index(i), schedulingGate.Name))
|
||||||
|
}
|
||||||
|
seen.Insert(schedulingGate.Name)
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
func validatePodDNSConfig(dnsConfig *core.PodDNSConfig, dnsPolicy *core.DNSPolicy, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
func validatePodDNSConfig(dnsConfig *core.PodDNSConfig, dnsPolicy *core.DNSPolicy, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
@ -3420,6 +3436,28 @@ func validateOnlyAddedTolerations(newTolerations []core.Toleration, oldToleratio
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateOnlyDeletedSchedulingGates(newGates, oldGates []core.PodSchedulingGate, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
if len(newGates) == 0 {
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
additionalGates := make(map[string]int)
|
||||||
|
for i, newGate := range newGates {
|
||||||
|
additionalGates[newGate.Name] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, oldGate := range oldGates {
|
||||||
|
delete(additionalGates, oldGate.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
for gate, i := range additionalGates {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(fldPath.Index(i).Child("name"), fmt.Sprintf("only deletion is allowed, but found new scheduling gate '%s'", gate)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
func ValidateHostAliases(hostAliases []core.HostAlias, fldPath *field.Path) field.ErrorList {
|
func ValidateHostAliases(hostAliases []core.HostAlias, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
for _, hostAlias := range hostAliases {
|
for _, hostAlias := range hostAliases {
|
||||||
@ -3611,6 +3649,7 @@ func ValidatePodSpec(spec *core.PodSpec, podMeta *metav1.ObjectMeta, fldPath *fi
|
|||||||
allErrs = append(allErrs, validateAffinity(spec.Affinity, fldPath.Child("affinity"))...)
|
allErrs = append(allErrs, validateAffinity(spec.Affinity, fldPath.Child("affinity"))...)
|
||||||
allErrs = append(allErrs, validatePodDNSConfig(spec.DNSConfig, &spec.DNSPolicy, fldPath.Child("dnsConfig"), opts)...)
|
allErrs = append(allErrs, validatePodDNSConfig(spec.DNSConfig, &spec.DNSPolicy, fldPath.Child("dnsConfig"), opts)...)
|
||||||
allErrs = append(allErrs, validateReadinessGates(spec.ReadinessGates, fldPath.Child("readinessGates"))...)
|
allErrs = append(allErrs, validateReadinessGates(spec.ReadinessGates, fldPath.Child("readinessGates"))...)
|
||||||
|
allErrs = append(allErrs, validateSchedulingGates(spec.SchedulingGates, fldPath.Child("schedulingGates"))...)
|
||||||
allErrs = append(allErrs, validateTopologySpreadConstraints(spec.TopologySpreadConstraints, fldPath.Child("topologySpreadConstraints"))...)
|
allErrs = append(allErrs, validateTopologySpreadConstraints(spec.TopologySpreadConstraints, fldPath.Child("topologySpreadConstraints"))...)
|
||||||
allErrs = append(allErrs, validateWindowsHostProcessPod(spec, fldPath)...)
|
allErrs = append(allErrs, validateWindowsHostProcessPod(spec, fldPath)...)
|
||||||
allErrs = append(allErrs, validateHostUsers(spec, fldPath)...)
|
allErrs = append(allErrs, validateHostUsers(spec, fldPath)...)
|
||||||
@ -4285,6 +4324,11 @@ func ValidatePodCreate(pod *core.Pod, opts PodValidationOptions) field.ErrorList
|
|||||||
if len(pod.Spec.EphemeralContainers) > 0 {
|
if len(pod.Spec.EphemeralContainers) > 0 {
|
||||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("ephemeralContainers"), "cannot be set on create"))
|
allErrs = append(allErrs, field.Forbidden(fldPath.Child("ephemeralContainers"), "cannot be set on create"))
|
||||||
}
|
}
|
||||||
|
// A Pod cannot be assigned a Node if there are remaining scheduling gates.
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness) &&
|
||||||
|
pod.Spec.NodeName != "" && len(pod.Spec.SchedulingGates) != 0 {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared"))
|
||||||
|
}
|
||||||
allErrs = append(allErrs, validateSeccompAnnotationsAndFields(pod.ObjectMeta, &pod.Spec, fldPath)...)
|
allErrs = append(allErrs, validateSeccompAnnotationsAndFields(pod.ObjectMeta, &pod.Spec, fldPath)...)
|
||||||
|
|
||||||
return allErrs
|
return allErrs
|
||||||
@ -4370,6 +4414,7 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
|
|||||||
// 2. spec.initContainers[*].image
|
// 2. spec.initContainers[*].image
|
||||||
// 3. spec.activeDeadlineSeconds
|
// 3. spec.activeDeadlineSeconds
|
||||||
// 4. spec.terminationGracePeriodSeconds
|
// 4. spec.terminationGracePeriodSeconds
|
||||||
|
// 5. spec.schedulingGates
|
||||||
|
|
||||||
containerErrs, stop := ValidateContainerUpdates(newPod.Spec.Containers, oldPod.Spec.Containers, specPath.Child("containers"))
|
containerErrs, stop := ValidateContainerUpdates(newPod.Spec.Containers, oldPod.Spec.Containers, specPath.Child("containers"))
|
||||||
allErrs = append(allErrs, containerErrs...)
|
allErrs = append(allErrs, containerErrs...)
|
||||||
@ -4405,6 +4450,9 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
|
|||||||
// Allow only additions to tolerations updates.
|
// Allow only additions to tolerations updates.
|
||||||
allErrs = append(allErrs, validateOnlyAddedTolerations(newPod.Spec.Tolerations, oldPod.Spec.Tolerations, specPath.Child("tolerations"))...)
|
allErrs = append(allErrs, validateOnlyAddedTolerations(newPod.Spec.Tolerations, oldPod.Spec.Tolerations, specPath.Child("tolerations"))...)
|
||||||
|
|
||||||
|
// Allow only deletions to schedulingGates updates.
|
||||||
|
allErrs = append(allErrs, validateOnlyDeletedSchedulingGates(newPod.Spec.SchedulingGates, oldPod.Spec.SchedulingGates, specPath.Child("schedulingGates"))...)
|
||||||
|
|
||||||
// the last thing to check is pod spec equality. If the pod specs are equal, then we can simply return the errors we have
|
// the last thing to check is pod spec equality. If the pod specs are equal, then we can simply return the errors we have
|
||||||
// so far and save the cost of a deep copy.
|
// so far and save the cost of a deep copy.
|
||||||
if apiequality.Semantic.DeepEqual(newPod.Spec, oldPod.Spec) {
|
if apiequality.Semantic.DeepEqual(newPod.Spec, oldPod.Spec) {
|
||||||
@ -4433,6 +4481,8 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
|
|||||||
activeDeadlineSeconds := *oldPod.Spec.ActiveDeadlineSeconds
|
activeDeadlineSeconds := *oldPod.Spec.ActiveDeadlineSeconds
|
||||||
mungedPodSpec.ActiveDeadlineSeconds = &activeDeadlineSeconds
|
mungedPodSpec.ActiveDeadlineSeconds = &activeDeadlineSeconds
|
||||||
}
|
}
|
||||||
|
// munge spec.schedulingGates
|
||||||
|
mungedPodSpec.SchedulingGates = oldPod.Spec.SchedulingGates // +k8s:verify-mutation:reason=clone
|
||||||
// tolerations are checked before the deep copy, so munge those too
|
// tolerations are checked before the deep copy, so munge those too
|
||||||
mungedPodSpec.Tolerations = oldPod.Spec.Tolerations // +k8s:verify-mutation:reason=clone
|
mungedPodSpec.Tolerations = oldPod.Spec.Tolerations // +k8s:verify-mutation:reason=clone
|
||||||
|
|
||||||
|
@ -10794,6 +10794,91 @@ func TestValidatePod(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidatePodCreateWithSchedulingGates(t *testing.T) {
|
||||||
|
applyEssentials := func(pod *core.Pod) {
|
||||||
|
pod.Spec.Containers = []core.Container{
|
||||||
|
{Name: "con", Image: "pause", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
|
||||||
|
}
|
||||||
|
pod.Spec.RestartPolicy = core.RestartPolicyAlways
|
||||||
|
pod.Spec.DNSPolicy = core.DNSClusterFirst
|
||||||
|
}
|
||||||
|
fldPath := field.NewPath("spec")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pod *core.Pod
|
||||||
|
featureEnabled bool
|
||||||
|
wantFieldErrors field.ErrorList
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "create a Pod with nodeName and schedulingGates, feature disabled",
|
||||||
|
pod: &core.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
NodeName: "node",
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
featureEnabled: false,
|
||||||
|
wantFieldErrors: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create a Pod with nodeName and schedulingGates, feature enabled",
|
||||||
|
pod: &core.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
NodeName: "node",
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
featureEnabled: true,
|
||||||
|
wantFieldErrors: []*field.Error{field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create a Pod with schedulingGates, feature disabled",
|
||||||
|
pod: &core.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
featureEnabled: false,
|
||||||
|
wantFieldErrors: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create a Pod with schedulingGates, feature enabled",
|
||||||
|
pod: &core.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
featureEnabled: true,
|
||||||
|
wantFieldErrors: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, tt.featureEnabled)()
|
||||||
|
|
||||||
|
applyEssentials(tt.pod)
|
||||||
|
errs := ValidatePodCreate(tt.pod, PodValidationOptions{})
|
||||||
|
if diff := cmp.Diff(tt.wantFieldErrors, errs); diff != "" {
|
||||||
|
t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestValidatePodUpdate(t *testing.T) {
|
func TestValidatePodUpdate(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
activeDeadlineSecondsZero = int64(0)
|
activeDeadlineSecondsZero = int64(0)
|
||||||
@ -11698,6 +11783,54 @@ func TestValidatePodUpdate(t *testing.T) {
|
|||||||
err: "Forbidden: pod updates may not change fields other than ",
|
err: "Forbidden: pod updates may not change fields other than ",
|
||||||
test: "update pod spec OS to a valid value, featuregate disabled",
|
test: "update pod spec OS to a valid value, featuregate disabled",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
new: core.Pod{
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
old: core.Pod{},
|
||||||
|
err: "Forbidden: only deletion is allowed, but found new scheduling gate 'foo'",
|
||||||
|
test: "update pod spec schedulingGates: add new scheduling gate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: core.Pod{
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{{Name: "bar"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
old: core.Pod{
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: "Forbidden: only deletion is allowed, but found new scheduling gate 'bar'",
|
||||||
|
test: "update pod spec schedulingGates: mutating an existing scheduling gate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: core.Pod{
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
old: core.Pod{
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}, {Name: "bar"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: "Forbidden: only deletion is allowed, but found new scheduling gate 'baz'",
|
||||||
|
test: "update pod spec schedulingGates: mutating an existing scheduling gate along with deletion",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: core.Pod{},
|
||||||
|
old: core.Pod{
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: "",
|
||||||
|
test: "update pod spec schedulingGates: legal deletion",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
test.new.ObjectMeta.ResourceVersion = "1"
|
test.new.ObjectMeta.ResourceVersion = "1"
|
||||||
@ -18484,6 +18617,7 @@ func TestValidateOSFields(t *testing.T) {
|
|||||||
"RestartPolicy",
|
"RestartPolicy",
|
||||||
"RuntimeClassName",
|
"RuntimeClassName",
|
||||||
"SchedulerName",
|
"SchedulerName",
|
||||||
|
"SchedulingGates[*].Name",
|
||||||
"SecurityContext.RunAsNonRoot",
|
"SecurityContext.RunAsNonRoot",
|
||||||
"ServiceAccountName",
|
"ServiceAccountName",
|
||||||
"SetHostnameAsFQDN",
|
"SetHostnameAsFQDN",
|
||||||
@ -18520,6 +18654,71 @@ func TestValidateOSFields(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateSchedulingGates(t *testing.T) {
|
||||||
|
fieldPath := field.NewPath("field")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
schedulingGates []core.PodSchedulingGate
|
||||||
|
wantFieldErrors field.ErrorList
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil gates",
|
||||||
|
schedulingGates: nil,
|
||||||
|
wantFieldErrors: field.ErrorList{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty string in gates",
|
||||||
|
schedulingGates: []core.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
{Name: ""},
|
||||||
|
},
|
||||||
|
wantFieldErrors: []*field.Error{field.Required(fieldPath.Index(1), "must not be empty")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "legal gates",
|
||||||
|
schedulingGates: []core.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
{Name: "bar"},
|
||||||
|
},
|
||||||
|
wantFieldErrors: field.ErrorList{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duplicated gates (single duplication)",
|
||||||
|
schedulingGates: []core.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
{Name: "bar"},
|
||||||
|
{Name: "bar"},
|
||||||
|
},
|
||||||
|
wantFieldErrors: []*field.Error{field.Duplicate(fieldPath.Index(2), "bar")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duplicated gates (multiple duplications)",
|
||||||
|
schedulingGates: []core.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
{Name: "bar"},
|
||||||
|
{Name: "foo"},
|
||||||
|
{Name: "baz"},
|
||||||
|
{Name: "foo"},
|
||||||
|
{Name: "bar"},
|
||||||
|
},
|
||||||
|
wantFieldErrors: field.ErrorList{
|
||||||
|
field.Duplicate(fieldPath.Index(2), "foo"),
|
||||||
|
field.Duplicate(fieldPath.Index(4), "foo"),
|
||||||
|
field.Duplicate(fieldPath.Index(5), "bar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
errs := validateSchedulingGates(tt.schedulingGates, fieldPath)
|
||||||
|
if diff := cmp.Diff(tt.wantFieldErrors, errs); diff != "" {
|
||||||
|
t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// collectResourcePaths traverses the object, computing all the struct paths.
|
// collectResourcePaths traverses the object, computing all the struct paths.
|
||||||
func collectResourcePaths(t *testing.T, skipRecurseList sets.String, tp reflect.Type, path *field.Path) sets.String {
|
func collectResourcePaths(t *testing.T, skipRecurseList sets.String, tp reflect.Type, path *field.Path) sets.String {
|
||||||
if pathStr := path.String(); len(pathStr) > 0 && skipRecurseList.Has(pathStr) {
|
if pathStr := path.String(); len(pathStr) > 0 && skipRecurseList.Has(pathStr) {
|
||||||
|
@ -643,6 +643,13 @@ const (
|
|||||||
// sandbox creation and network configuration completes successfully
|
// sandbox creation and network configuration completes successfully
|
||||||
PodHasNetworkCondition featuregate.Feature = "PodHasNetworkCondition"
|
PodHasNetworkCondition featuregate.Feature = "PodHasNetworkCondition"
|
||||||
|
|
||||||
|
// owner: @Huang-Wei
|
||||||
|
// kep: https://kep.k8s.io/3521
|
||||||
|
// alpha: v1.26
|
||||||
|
//
|
||||||
|
// Enable users to specify when a Pod is ready for scheduling.
|
||||||
|
PodSchedulingReadiness featuregate.Feature = "PodSchedulingReadiness"
|
||||||
|
|
||||||
// owner: @liggitt, @tallclair, sig-auth
|
// owner: @liggitt, @tallclair, sig-auth
|
||||||
// alpha: v1.22
|
// alpha: v1.22
|
||||||
// beta: v1.23
|
// beta: v1.23
|
||||||
@ -995,6 +1002,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
|||||||
|
|
||||||
PodHasNetworkCondition: {Default: false, PreRelease: featuregate.Alpha},
|
PodHasNetworkCondition: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
|
PodSchedulingReadiness: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
PodSecurity: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
PodSecurity: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
|
|
||||||
ProbeTerminationGracePeriod: {Default: true, PreRelease: featuregate.Beta}, // Default to true in beta 1.25
|
ProbeTerminationGracePeriod: {Default: true, PreRelease: featuregate.Beta}, // Default to true in beta 1.25
|
||||||
|
@ -767,6 +767,13 @@ func printPod(pod *api.Pod, options printers.GenerateOptions) ([]metav1.TableRow
|
|||||||
reason = pod.Status.Reason
|
reason = pod.Status.Reason
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the Pod carries {type:PodScheduled, reason:WaitingForGates}, set reason to 'SchedulingGated'.
|
||||||
|
for _, condition := range pod.Status.Conditions {
|
||||||
|
if condition.Type == api.PodScheduled && condition.Reason == api.PodReasonSchedulingGated {
|
||||||
|
reason = api.PodReasonSchedulingGated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
row := metav1.TableRow{
|
row := metav1.TableRow{
|
||||||
Object: runtime.RawExtension{Object: pod},
|
Object: runtime.RawExtension{Object: pod},
|
||||||
}
|
}
|
||||||
|
@ -1502,6 +1502,24 @@ func TestPrintPod(t *testing.T) {
|
|||||||
},
|
},
|
||||||
[]metav1.TableRow{{Cells: []interface{}{"test14", "2/2", "Running", "9 (5d ago)", "<unknown>"}}},
|
[]metav1.TableRow{{Cells: []interface{}{"test14", "2/2", "Running", "9 (5d ago)", "<unknown>"}}},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Test PodScheduled condition with reason WaitingForGates
|
||||||
|
api.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "test15"},
|
||||||
|
Spec: api.PodSpec{Containers: make([]api.Container, 2)},
|
||||||
|
Status: api.PodStatus{
|
||||||
|
Phase: "podPhase",
|
||||||
|
Conditions: []api.PodCondition{
|
||||||
|
{
|
||||||
|
Type: api.PodScheduled,
|
||||||
|
Status: api.ConditionFalse,
|
||||||
|
Reason: api.PodReasonSchedulingGated,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]metav1.TableRow{{Cells: []interface{}{"test15", "0/2", api.PodReasonSchedulingGated, "0", "<unknown>"}}},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
|
@ -33,10 +33,12 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/storage"
|
"k8s.io/apiserver/pkg/storage"
|
||||||
storeerr "k8s.io/apiserver/pkg/storage/errors"
|
storeerr "k8s.io/apiserver/pkg/storage/errors"
|
||||||
"k8s.io/apiserver/pkg/util/dryrun"
|
"k8s.io/apiserver/pkg/util/dryrun"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
policyclient "k8s.io/client-go/kubernetes/typed/policy/v1"
|
policyclient "k8s.io/client-go/kubernetes/typed/policy/v1"
|
||||||
podutil "k8s.io/kubernetes/pkg/api/pod"
|
podutil "k8s.io/kubernetes/pkg/api/pod"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/client"
|
"k8s.io/kubernetes/pkg/kubelet/client"
|
||||||
"k8s.io/kubernetes/pkg/printers"
|
"k8s.io/kubernetes/pkg/printers"
|
||||||
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
||||||
@ -221,6 +223,10 @@ func (r *BindingREST) setPodHostAndAnnotations(ctx context.Context, podUID types
|
|||||||
if pod.Spec.NodeName != "" {
|
if pod.Spec.NodeName != "" {
|
||||||
return nil, fmt.Errorf("pod %v is already assigned to node %q", pod.Name, pod.Spec.NodeName)
|
return nil, fmt.Errorf("pod %v is already assigned to node %q", pod.Name, pod.Spec.NodeName)
|
||||||
}
|
}
|
||||||
|
// Reject binding to a scheduling un-ready Pod.
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness) && len(pod.Spec.SchedulingGates) != 0 {
|
||||||
|
return nil, fmt.Errorf("pod %v has non-empty .spec.schedulingGates", pod.Name)
|
||||||
|
}
|
||||||
pod.Spec.NodeName = machine
|
pod.Spec.NodeName = machine
|
||||||
if pod.Annotations == nil {
|
if pod.Annotations == nil {
|
||||||
pod.Annotations = make(map[string]string)
|
pod.Annotations = make(map[string]string)
|
||||||
|
@ -18,6 +18,7 @@ package storage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
goerrors "errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
@ -41,7 +42,10 @@ import (
|
|||||||
apiserverstorage "k8s.io/apiserver/pkg/storage"
|
apiserverstorage "k8s.io/apiserver/pkg/storage"
|
||||||
storeerr "k8s.io/apiserver/pkg/storage/errors"
|
storeerr "k8s.io/apiserver/pkg/storage/errors"
|
||||||
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
|
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/registry/registrytest"
|
"k8s.io/kubernetes/pkg/registry/registrytest"
|
||||||
"k8s.io/kubernetes/pkg/securitycontext"
|
"k8s.io/kubernetes/pkg/securitycontext"
|
||||||
)
|
)
|
||||||
@ -745,6 +749,77 @@ func TestEtcdCreateWithConflict(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEtcdCreateWithSchedulingGates(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
featureEnabled bool
|
||||||
|
schedulingGates []api.PodSchedulingGate
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "pod with non-nil schedulingGates, feature disabled",
|
||||||
|
featureEnabled: false,
|
||||||
|
schedulingGates: []api.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
{Name: "bar"},
|
||||||
|
},
|
||||||
|
wantErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pod with non-nil schedulingGates, feature enabled",
|
||||||
|
featureEnabled: true,
|
||||||
|
schedulingGates: []api.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
{Name: "bar"},
|
||||||
|
},
|
||||||
|
wantErr: goerrors.New(`Operation cannot be fulfilled on pods/binding "foo": pod foo has non-empty .spec.schedulingGates`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pod with nil schedulingGates, feature disabled",
|
||||||
|
featureEnabled: false,
|
||||||
|
schedulingGates: nil,
|
||||||
|
wantErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pod with nil schedulingGates, feature enabled",
|
||||||
|
featureEnabled: true,
|
||||||
|
schedulingGates: nil,
|
||||||
|
wantErr: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, tt.featureEnabled)()
|
||||||
|
storage, bindingStorage, _, server := newStorage(t)
|
||||||
|
defer server.Terminate(t)
|
||||||
|
defer storage.Store.DestroyFunc()
|
||||||
|
ctx := genericapirequest.NewDefaultContext()
|
||||||
|
|
||||||
|
pod := validNewPod()
|
||||||
|
pod.Spec.SchedulingGates = tt.schedulingGates
|
||||||
|
if _, err := storage.Create(ctx, pod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
_, err := bindingStorage.Create(ctx, "foo", &api.Binding{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "foo"},
|
||||||
|
Target: api.ObjectReference{Name: "machine"},
|
||||||
|
}, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
|
||||||
|
if tt.wantErr == nil {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Want nil err, but got %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Want %v, but got nil err", tt.wantErr)
|
||||||
|
} else if tt.wantErr.Error() != err.Error() {
|
||||||
|
t.Errorf("Want %v, but got %v", tt.wantErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func validNewBinding() *api.Binding {
|
func validNewBinding() *api.Binding {
|
||||||
return &api.Binding{
|
return &api.Binding{
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||||
|
@ -38,12 +38,14 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/registry/generic"
|
"k8s.io/apiserver/pkg/registry/generic"
|
||||||
"k8s.io/apiserver/pkg/storage"
|
"k8s.io/apiserver/pkg/storage"
|
||||||
"k8s.io/apiserver/pkg/storage/names"
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
podutil "k8s.io/kubernetes/pkg/api/pod"
|
podutil "k8s.io/kubernetes/pkg/api/pod"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/helper/qos"
|
"k8s.io/kubernetes/pkg/apis/core/helper/qos"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/client"
|
"k8s.io/kubernetes/pkg/kubelet/client"
|
||||||
proxyutil "k8s.io/kubernetes/pkg/proxy/util"
|
proxyutil "k8s.io/kubernetes/pkg/proxy/util"
|
||||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
@ -87,6 +89,7 @@ func (podStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
|||||||
podutil.DropDisabledPodFields(pod, nil)
|
podutil.DropDisabledPodFields(pod, nil)
|
||||||
|
|
||||||
applySeccompVersionSkew(pod)
|
applySeccompVersionSkew(pod)
|
||||||
|
applyWaitingForSchedulingGatesCondition(pod)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
||||||
@ -642,6 +645,29 @@ func validateContainer(container string, pod *api.Pod) (string, error) {
|
|||||||
return container, nil
|
return container, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// applyWaitingForSchedulingGatesCondition adds a {type:PodScheduled, reason:WaitingForGates} condition
|
||||||
|
// to a new-created Pod if necessary.
|
||||||
|
func applyWaitingForSchedulingGatesCondition(pod *api.Pod) {
|
||||||
|
if !utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness) ||
|
||||||
|
len(pod.Spec.SchedulingGates) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If found a condition with type PodScheduled, return.
|
||||||
|
for _, condition := range pod.Status.Conditions {
|
||||||
|
if condition.Type == api.PodScheduled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pod.Status.Conditions = append(pod.Status.Conditions, api.PodCondition{
|
||||||
|
Type: api.PodScheduled,
|
||||||
|
Status: api.ConditionFalse,
|
||||||
|
Reason: api.PodReasonSchedulingGated,
|
||||||
|
Message: "Scheduling is blocked due to non-empty scheduling gates",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// applySeccompVersionSkew implements the version skew behavior described in:
|
// applySeccompVersionSkew implements the version skew behavior described in:
|
||||||
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/135-seccomp#version-skew-strategy
|
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/135-seccomp#version-skew-strategy
|
||||||
// Note that we dropped copying the field to annotation synchronization in
|
// Note that we dropped copying the field to annotation synchronization in
|
||||||
|
@ -35,9 +35,12 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
apitesting "k8s.io/kubernetes/pkg/api/testing"
|
apitesting "k8s.io/kubernetes/pkg/api/testing"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/client"
|
"k8s.io/kubernetes/pkg/kubelet/client"
|
||||||
utilpointer "k8s.io/utils/pointer"
|
utilpointer "k8s.io/utils/pointer"
|
||||||
|
|
||||||
@ -261,6 +264,72 @@ func TestGetPodQOS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWaitingForGatesCondition(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pod *api.Pod
|
||||||
|
featureEnabled bool
|
||||||
|
want api.PodCondition
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "pod without .spec.schedulingGates, feature disabled",
|
||||||
|
pod: &api.Pod{},
|
||||||
|
featureEnabled: false,
|
||||||
|
want: api.PodCondition{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pod without .spec.schedulingGates, feature enabled",
|
||||||
|
pod: &api.Pod{},
|
||||||
|
featureEnabled: true,
|
||||||
|
want: api.PodCondition{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pod with .spec.schedulingGates, feature disabled",
|
||||||
|
pod: &api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
SchedulingGates: []api.PodSchedulingGate{{Name: "foo"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
featureEnabled: false,
|
||||||
|
want: api.PodCondition{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pod with .spec.schedulingGates, feature enabled",
|
||||||
|
pod: &api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
SchedulingGates: []api.PodSchedulingGate{{Name: "foo"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
featureEnabled: true,
|
||||||
|
want: api.PodCondition{
|
||||||
|
Type: api.PodScheduled,
|
||||||
|
Status: api.ConditionFalse,
|
||||||
|
Reason: api.PodReasonSchedulingGated,
|
||||||
|
Message: "Scheduling is blocked due to non-empty scheduling gates",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, tt.featureEnabled)()
|
||||||
|
|
||||||
|
Strategy.PrepareForCreate(genericapirequest.NewContext(), tt.pod)
|
||||||
|
var got api.PodCondition
|
||||||
|
for _, condition := range tt.pod.Status.Conditions {
|
||||||
|
if condition.Type == api.PodScheduled {
|
||||||
|
got = condition
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := cmp.Diff(tt.want, got); diff != "" {
|
||||||
|
t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCheckGracefulDelete(t *testing.T) {
|
func TestCheckGracefulDelete(t *testing.T) {
|
||||||
defaultGracePeriod := int64(30)
|
defaultGracePeriod := int64(30)
|
||||||
tcs := []struct {
|
tcs := []struct {
|
||||||
|
@ -2666,6 +2666,10 @@ const (
|
|||||||
// can't schedule the pod right now, for example due to insufficient resources in the cluster.
|
// can't schedule the pod right now, for example due to insufficient resources in the cluster.
|
||||||
PodReasonUnschedulable = "Unschedulable"
|
PodReasonUnschedulable = "Unschedulable"
|
||||||
|
|
||||||
|
// PodReasonSchedulingGated reason in PodScheduled PodCondition means that the scheduler
|
||||||
|
// skips scheduling the pod because one or more scheduling gates are still present.
|
||||||
|
PodReasonSchedulingGated = "SchedulingGated"
|
||||||
|
|
||||||
// PodReasonSchedulerError reason in PodScheduled PodCondition means that some internal error happens
|
// PodReasonSchedulerError reason in PodScheduled PodCondition means that some internal error happens
|
||||||
// during scheduling, for example due to nodeAffinity parsing errors.
|
// during scheduling, for example due to nodeAffinity parsing errors.
|
||||||
PodReasonSchedulerError = "SchedulerError"
|
PodReasonSchedulerError = "SchedulerError"
|
||||||
@ -3327,6 +3331,16 @@ type PodSpec struct {
|
|||||||
// +k8s:conversion-gen=false
|
// +k8s:conversion-gen=false
|
||||||
// +optional
|
// +optional
|
||||||
HostUsers *bool `json:"hostUsers,omitempty" protobuf:"bytes,37,opt,name=hostUsers"`
|
HostUsers *bool `json:"hostUsers,omitempty" protobuf:"bytes,37,opt,name=hostUsers"`
|
||||||
|
// SchedulingGates is an opaque list of values that if specified will block scheduling the pod.
|
||||||
|
// More info: https://git.k8s.io/enhancements/keps/sig-scheduling/3521-pod-scheduling-readiness.
|
||||||
|
//
|
||||||
|
// This is an alpha-level feature enabled by PodSchedulingReadiness feature gate.
|
||||||
|
// +optional
|
||||||
|
// +patchMergeKey=name
|
||||||
|
// +patchStrategy=merge
|
||||||
|
// +listType=map
|
||||||
|
// +listMapKey=name
|
||||||
|
SchedulingGates []PodSchedulingGate `json:"schedulingGates,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,38,opt,name=schedulingGates"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OSName is the set of OS'es that can be used in OS.
|
// OSName is the set of OS'es that can be used in OS.
|
||||||
@ -3347,6 +3361,13 @@ type PodOS struct {
|
|||||||
Name OSName `json:"name" protobuf:"bytes,1,opt,name=name"`
|
Name OSName `json:"name" protobuf:"bytes,1,opt,name=name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PodSchedulingGate is associated to a Pod to guard its scheduling.
|
||||||
|
type PodSchedulingGate struct {
|
||||||
|
// Name of the scheduling gate.
|
||||||
|
// Each scheduling gate must have a unique name field.
|
||||||
|
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
|
||||||
|
}
|
||||||
|
|
||||||
// +enum
|
// +enum
|
||||||
type UnsatisfiableConstraintAction string
|
type UnsatisfiableConstraintAction string
|
||||||
|
|
||||||
|
@ -184,6 +184,7 @@ INFO: Unexpected error: wait for pod pending-pod running:
|
|||||||
SetHostnameAsFQDN: nil,
|
SetHostnameAsFQDN: nil,
|
||||||
OS: nil,
|
OS: nil,
|
||||||
HostUsers: nil,
|
HostUsers: nil,
|
||||||
|
SchedulingGates: nil,
|
||||||
},
|
},
|
||||||
Status: {
|
Status: {
|
||||||
Phase: "",
|
Phase: "",
|
||||||
|
Loading…
Reference in New Issue
Block a user