mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-07 03:03:59 +00:00
Merge pull request #123575 from Huang-Wei/pod-scheduling-readiness-stable
Graduate PodSchedulingReadiness to stable
This commit is contained in:
commit
e4a14fe0f5
2
api/openapi-spec/swagger.json
generated
2
api/openapi-spec/swagger.json
generated
@ -9046,7 +9046,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"schedulingGates": {
|
||||
"description": "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. If schedulingGates is not empty, the pod will stay in the SchedulingGated state and the scheduler will not attempt to schedule the pod.\n\nSchedulingGates can only be set at pod creation time, and be removed only afterwards.\n\nThis is a beta feature enabled by the PodSchedulingReadiness feature gate.",
|
||||
"description": "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. If schedulingGates is not empty, the pod will stay in the SchedulingGated state and the scheduler will not attempt to schedule the pod.\n\nSchedulingGates can only be set at pod creation time, and be removed only afterwards.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/io.k8s.api.core.v1.PodSchedulingGate"
|
||||
},
|
||||
|
@ -5465,7 +5465,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"schedulingGates": {
|
||||
"description": "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. If schedulingGates is not empty, the pod will stay in the SchedulingGated state and the scheduler will not attempt to schedule the pod.\n\nSchedulingGates can only be set at pod creation time, and be removed only afterwards.\n\nThis is a beta feature enabled by the PodSchedulingReadiness feature gate.",
|
||||
"description": "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. If schedulingGates is not empty, the pod will stay in the SchedulingGated state and the scheduler will not attempt to schedule the pod.\n\nSchedulingGates can only be set at pod creation time, and be removed only afterwards.",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
|
@ -3882,7 +3882,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"schedulingGates": {
|
||||
"description": "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. If schedulingGates is not empty, the pod will stay in the SchedulingGated state and the scheduler will not attempt to schedule the pod.\n\nSchedulingGates can only be set at pod creation time, and be removed only afterwards.\n\nThis is a beta feature enabled by the PodSchedulingReadiness feature gate.",
|
||||
"description": "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. If schedulingGates is not empty, the pod will stay in the SchedulingGated state and the scheduler will not attempt to schedule the pod.\n\nSchedulingGates can only be set at pod creation time, and be removed only afterwards.",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
|
@ -3037,7 +3037,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"schedulingGates": {
|
||||
"description": "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. If schedulingGates is not empty, the pod will stay in the SchedulingGated state and the scheduler will not attempt to schedule the pod.\n\nSchedulingGates can only be set at pod creation time, and be removed only afterwards.\n\nThis is a beta feature enabled by the PodSchedulingReadiness feature gate.",
|
||||
"description": "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. If schedulingGates is not empty, the pod will stay in the SchedulingGated state and the scheduler will not attempt to schedule the pod.\n\nSchedulingGates can only be set at pod creation time, and be removed only afterwards.",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
|
@ -239,23 +239,6 @@ leaderElection:
|
||||
"simplified-scheduler": defaults.ExpandedPluginsV1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "default config with a beta feature disabled",
|
||||
flags: []string{
|
||||
"--kubeconfig", configKubeconfig,
|
||||
"--feature-gates=PodSchedulingReadiness=false",
|
||||
},
|
||||
wantPlugins: map[string]*config.Plugins{
|
||||
"default-scheduler": func() *config.Plugins {
|
||||
plugins := defaults.ExpandedPluginsV1.DeepCopy()
|
||||
plugins.PreEnqueue = config.PluginSet{}
|
||||
return plugins
|
||||
}(),
|
||||
},
|
||||
restoreFeatures: map[featuregate.Feature]bool{
|
||||
features.PodSchedulingReadiness: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "default config",
|
||||
flags: []string{
|
||||
|
@ -382,7 +382,6 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
|
||||
AllowIndivisibleHugePagesValues: false,
|
||||
AllowInvalidLabelValueInSelector: false,
|
||||
AllowInvalidTopologySpreadConstraintLabelSelector: false,
|
||||
AllowMutableNodeSelectorAndNodeAffinity: utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness),
|
||||
AllowNamespacedSysctlsForHostNetAndHostIPC: false,
|
||||
AllowNonLocalProjectedTokenPath: false,
|
||||
}
|
||||
@ -558,11 +557,6 @@ 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)
|
||||
|
||||
dropDisabledTopologySpreadConstraintsFields(podSpec, oldPodSpec)
|
||||
@ -983,14 +977,6 @@ func appArmorInUse(podAnnotations map[string]string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// restartableInitContainersInUse returns true if the pod spec is non-nil and
|
||||
// it has any init container with ContainerRestartPolicyAlways.
|
||||
func restartableInitContainersInUse(podSpec *api.PodSpec) bool {
|
||||
|
@ -2563,88 +2563,6 @@ 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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateTopologySpreadConstraintLabelSelectorOption(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
@ -3352,9 +3352,6 @@ type PodSpec struct {
|
||||
//
|
||||
// SchedulingGates can only be set at pod creation time, and be removed only afterwards.
|
||||
//
|
||||
// This is a beta feature enabled by the PodSchedulingReadiness feature gate.
|
||||
//
|
||||
// +featureGate=PodSchedulingReadiness
|
||||
// +optional
|
||||
SchedulingGates []PodSchedulingGate
|
||||
// ResourceClaims defines which ResourceClaims must be allocated
|
||||
|
@ -3974,8 +3974,6 @@ type PodValidationOptions struct {
|
||||
AllowHostIPsField bool
|
||||
// Allow invalid topologySpreadConstraint labelSelector for backward compatibility
|
||||
AllowInvalidTopologySpreadConstraintLabelSelector bool
|
||||
// Allow node selector additions for gated pods.
|
||||
AllowMutableNodeSelectorAndNodeAffinity bool
|
||||
// Allow projected token volumes with non-local paths
|
||||
AllowNonLocalProjectedTokenPath bool
|
||||
// Allow namespaced sysctls in hostNet and hostIPC pods
|
||||
@ -5056,7 +5054,7 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
|
||||
|
||||
// Handle validations specific to gated pods.
|
||||
podIsGated := len(oldPod.Spec.SchedulingGates) > 0
|
||||
if opts.AllowMutableNodeSelectorAndNodeAffinity && podIsGated {
|
||||
if podIsGated {
|
||||
// Additions to spec.nodeSelector are allowed (no deletions or mutations) for gated pods.
|
||||
if !apiequality.Semantic.DeepEqual(mungedPodSpec.NodeSelector, oldPod.Spec.NodeSelector) {
|
||||
allErrs = append(allErrs, validateNodeSelectorMutation(specPath.Child("nodeSelector"), mungedPodSpec.NodeSelector, oldPod.Spec.NodeSelector)...)
|
||||
|
@ -12298,22 +12298,8 @@ func TestValidatePodCreateWithSchedulingGates(t *testing.T) {
|
||||
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: []*field.Error{field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared")},
|
||||
}, {
|
||||
name: "create a Pod with nodeName and schedulingGates, feature enabled",
|
||||
pod: &core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
|
||||
@ -12324,20 +12310,7 @@ func TestValidatePodCreateWithSchedulingGates(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
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{
|
||||
@ -12348,15 +12321,12 @@ func TestValidatePodCreateWithSchedulingGates(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
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 != "" {
|
||||
@ -12383,7 +12353,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
tests := []struct {
|
||||
new core.Pod
|
||||
old core.Pod
|
||||
opts PodValidationOptions
|
||||
err string
|
||||
test string
|
||||
}{
|
||||
@ -13716,25 +13685,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
|
||||
test: "node selector is immutable when AllowMutableNodeSelector is false",
|
||||
}, {
|
||||
old: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
new: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
NodeSelector: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
test: "adding node selector is allowed for gated pods",
|
||||
}, {
|
||||
old: core.Pod{
|
||||
@ -13752,9 +13702,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
|
||||
test: "adding node selector is not allowed for non-gated pods",
|
||||
}, {
|
||||
@ -13771,9 +13718,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
err: "spec.nodeSelector: Invalid value:",
|
||||
test: "removing node selector is not allowed for gated pods",
|
||||
}, {
|
||||
@ -13784,10 +13728,7 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
new: core.Pod{},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
new: core.Pod{},
|
||||
err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
|
||||
test: "removing node selector is not allowed for non-gated pods",
|
||||
}, {
|
||||
@ -13807,9 +13748,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
test: "old pod spec has scheduling gate, new pod spec does not, and node selector is added",
|
||||
}, {
|
||||
old: core.Pod{
|
||||
@ -13828,9 +13766,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
err: "spec.nodeSelector: Invalid value:",
|
||||
test: "modifying value of existing node selector is not allowed",
|
||||
}, {
|
||||
@ -13880,9 +13815,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
test: "addition to nodeAffinity is allowed for gated pods",
|
||||
}, {
|
||||
old: core.Pod{
|
||||
@ -13911,9 +13843,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:",
|
||||
test: "old RequiredDuringSchedulingIgnoredDuringExecution is non-nil, new RequiredDuringSchedulingIgnoredDuringExecution is nil, pod is gated",
|
||||
}, {
|
||||
@ -13961,9 +13890,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
|
||||
test: "addition to nodeAffinity is not allowed for non-gated pods",
|
||||
}, {
|
||||
@ -14012,9 +13938,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
test: "old pod spec has scheduling gate, new pod spec does not, and node affinity addition occurs",
|
||||
}, {
|
||||
old: core.Pod{
|
||||
@ -14053,9 +13976,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
|
||||
test: "nodeAffinity deletion from MatchExpressions not allowed",
|
||||
}, {
|
||||
@ -14101,9 +14021,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
|
||||
test: "nodeAffinity deletion from MatchFields not allowed",
|
||||
}, {
|
||||
@ -14154,9 +14071,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
|
||||
test: "nodeAffinity modification of item in MatchExpressions not allowed",
|
||||
}, {
|
||||
@ -14206,9 +14120,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
|
||||
test: "nodeAffinity modification of item in MatchFields not allowed",
|
||||
}, {
|
||||
@ -14269,9 +14180,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:",
|
||||
test: "nodeSelectorTerms addition on gated pod should fail",
|
||||
}, {
|
||||
@ -14313,9 +14221,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
test: "preferredDuringSchedulingIgnoredDuringExecution can modified for gated pods",
|
||||
}, {
|
||||
old: core.Pod{
|
||||
@ -14365,9 +14270,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
test: "preferredDuringSchedulingIgnoredDuringExecution can have additions for gated pods",
|
||||
}, {
|
||||
old: core.Pod{
|
||||
@ -14394,9 +14296,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
test: "preferredDuringSchedulingIgnoredDuringExecution can have removals for gated pods",
|
||||
}, {
|
||||
old: core.Pod{
|
||||
@ -14423,9 +14322,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:",
|
||||
test: "new node affinity is nil",
|
||||
}, {
|
||||
@ -14453,9 +14349,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
test: "preferredDuringSchedulingIgnoredDuringExecution can have removals for gated pods",
|
||||
}, {
|
||||
old: core.Pod{
|
||||
@ -14490,9 +14383,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
err: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
|
||||
test: "empty NodeSelectorTerm (selects nothing) cannot become populated (selects something)",
|
||||
}, {
|
||||
@ -14520,9 +14410,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
test: "nil affinity can be mutated for gated pods",
|
||||
},
|
||||
{
|
||||
@ -14560,9 +14447,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
err: "pod updates may not change fields other than",
|
||||
test: "the podAffinity cannot be updated on gated pods",
|
||||
},
|
||||
@ -14601,9 +14485,6 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{
|
||||
AllowMutableNodeSelectorAndNodeAffinity: true,
|
||||
},
|
||||
err: "pod updates may not change fields other than",
|
||||
test: "the podAntiAffinity cannot be updated on gated pods",
|
||||
},
|
||||
@ -14634,7 +14515,7 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
test.old.Spec.RestartPolicy = "Always"
|
||||
}
|
||||
|
||||
errs := ValidatePodUpdate(&test.new, &test.old, test.opts)
|
||||
errs := ValidatePodUpdate(&test.new, &test.old, PodValidationOptions{})
|
||||
if test.err == "" {
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old)
|
||||
|
@ -614,6 +614,7 @@ const (
|
||||
// kep: https://kep.k8s.io/3521
|
||||
// alpha: v1.26
|
||||
// beta: v1.27
|
||||
// stable: v1.30
|
||||
//
|
||||
// Enable users to specify when a Pod is ready for scheduling.
|
||||
PodSchedulingReadiness featuregate.Feature = "PodSchedulingReadiness"
|
||||
@ -1097,7 +1098,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
||||
|
||||
PodLifecycleSleepAction: {Default: true, PreRelease: featuregate.Beta},
|
||||
|
||||
PodSchedulingReadiness: {Default: true, PreRelease: featuregate.Beta},
|
||||
PodSchedulingReadiness: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // GA in 1.30; remove in 1.32
|
||||
|
||||
ProcMountType: {Default: false, PreRelease: featuregate.Alpha},
|
||||
|
||||
|
2
pkg/generated/openapi/zz_generated.openapi.go
generated
2
pkg/generated/openapi/zz_generated.openapi.go
generated
@ -26465,7 +26465,7 @@ func schema_k8sio_api_core_v1_PodSpec(ref common.ReferenceCallback) common.OpenA
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. If schedulingGates is not empty, the pod will stay in the SchedulingGated state and the scheduler will not attempt to schedule the pod.\n\nSchedulingGates can only be set at pod creation time, and be removed only afterwards.\n\nThis is a beta feature enabled by the PodSchedulingReadiness feature gate.",
|
||||
Description: "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. If schedulingGates is not empty, the pod will stay in the SchedulingGated state and the scheduler will not attempt to schedule the pod.\n\nSchedulingGates can only be set at pod creation time, and be removed only afterwards.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
|
@ -42,10 +42,7 @@ import (
|
||||
apiserverstorage "k8s.io/apiserver/pkg/storage"
|
||||
storeerr "k8s.io/apiserver/pkg/storage/errors"
|
||||
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"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/registry/registrytest"
|
||||
"k8s.io/kubernetes/pkg/securitycontext"
|
||||
)
|
||||
@ -752,22 +749,11 @@ 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,
|
||||
name: "pod with non-nil schedulingGates",
|
||||
schedulingGates: []api.PodSchedulingGate{
|
||||
{Name: "foo"},
|
||||
{Name: "bar"},
|
||||
@ -775,56 +761,40 @@ func TestEtcdCreateWithSchedulingGates(t *testing.T) {
|
||||
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,
|
||||
name: "pod with nil schedulingGates",
|
||||
schedulingGates: nil,
|
||||
wantErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
for _, flipFeatureGateBeforeBinding := range []bool{false, true} {
|
||||
if flipFeatureGateBeforeBinding {
|
||||
tt.name = fmt.Sprintf("%v and flipped before binding", tt.name)
|
||||
}
|
||||
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()
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
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)
|
||||
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)
|
||||
}
|
||||
if flipFeatureGateBeforeBinding {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, !tt.featureEnabled)()
|
||||
} 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)
|
||||
}
|
||||
_, 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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -740,8 +740,7 @@ func mutatePodAffinity(pod *api.Pod) {
|
||||
// applySchedulingGatedCondition adds a {type:PodScheduled, reason:SchedulingGated} condition
|
||||
// to a new-created Pod if necessary.
|
||||
func applySchedulingGatedCondition(pod *api.Pod) {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness) ||
|
||||
len(pod.Spec.SchedulingGates) == 0 {
|
||||
if len(pod.Spec.SchedulingGates) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -314,41 +314,22 @@ func TestGetPodQOS(t *testing.T) {
|
||||
|
||||
func TestSchedulingGatedCondition(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pod *api.Pod
|
||||
featureEnabled bool
|
||||
want api.PodCondition
|
||||
name string
|
||||
pod *api.Pod
|
||||
want api.PodCondition
|
||||
}{
|
||||
{
|
||||
name: "pod without .spec.schedulingGates, feature disabled",
|
||||
pod: &api.Pod{},
|
||||
featureEnabled: false,
|
||||
want: api.PodCondition{},
|
||||
name: "pod without .spec.schedulingGates",
|
||||
pod: &api.Pod{},
|
||||
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",
|
||||
name: "pod with .spec.schedulingGates",
|
||||
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,
|
||||
@ -360,8 +341,6 @@ func TestSchedulingGatedCondition(t *testing.T) {
|
||||
|
||||
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 {
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
var PluginsV1 = &config.Plugins{
|
||||
MultiPoint: config.PluginSet{
|
||||
Enabled: []config.Plugin{
|
||||
{Name: names.SchedulingGates},
|
||||
{Name: names.PrioritySort},
|
||||
{Name: names.NodeUnschedulable},
|
||||
{Name: names.NodeName},
|
||||
@ -45,7 +46,6 @@ var PluginsV1 = &config.Plugins{
|
||||
{Name: names.NodeResourcesBalancedAllocation, Weight: 1},
|
||||
{Name: names.ImageLocality, Weight: 1},
|
||||
{Name: names.DefaultBinder},
|
||||
{Name: names.SchedulingGates},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ func getDefaultPlugins() *v1.Plugins {
|
||||
plugins := &v1.Plugins{
|
||||
MultiPoint: v1.PluginSet{
|
||||
Enabled: []v1.Plugin{
|
||||
{Name: names.SchedulingGates},
|
||||
{Name: names.PrioritySort},
|
||||
{Name: names.NodeUnschedulable},
|
||||
{Name: names.NodeName},
|
||||
@ -60,9 +61,6 @@ func getDefaultPlugins() *v1.Plugins {
|
||||
}
|
||||
|
||||
func applyFeatureGates(config *v1.Plugins) {
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness) {
|
||||
config.MultiPoint.Enabled = append(config.MultiPoint.Enabled, v1.Plugin{Name: names.SchedulingGates})
|
||||
}
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.DynamicResourceAllocation) {
|
||||
// This plugin should come before DefaultPreemption because if
|
||||
// there is a problem with a Pod and PostFilter gets called to
|
||||
|
@ -37,66 +37,34 @@ func TestApplyFeatureGates(t *testing.T) {
|
||||
wantConfig *v1.Plugins
|
||||
}{
|
||||
{
|
||||
name: "Feature gates disabled",
|
||||
name: "Feature gate DynamicResourceAllocation disabled",
|
||||
features: map[featuregate.Feature]bool{
|
||||
features.PodSchedulingReadiness: false,
|
||||
features.DynamicResourceAllocation: false,
|
||||
},
|
||||
wantConfig: &v1.Plugins{
|
||||
MultiPoint: v1.PluginSet{
|
||||
Enabled: []v1.Plugin{
|
||||
{Name: names.PrioritySort},
|
||||
{Name: names.NodeUnschedulable},
|
||||
{Name: names.NodeName},
|
||||
{Name: names.TaintToleration, Weight: ptr.To[int32](3)},
|
||||
{Name: names.NodeAffinity, Weight: ptr.To[int32](2)},
|
||||
{Name: names.NodePorts},
|
||||
{Name: names.NodeResourcesFit, Weight: ptr.To[int32](1)},
|
||||
{Name: names.VolumeRestrictions},
|
||||
{Name: names.EBSLimits},
|
||||
{Name: names.GCEPDLimits},
|
||||
{Name: names.NodeVolumeLimits},
|
||||
{Name: names.AzureDiskLimits},
|
||||
{Name: names.VolumeBinding},
|
||||
{Name: names.VolumeZone},
|
||||
{Name: names.PodTopologySpread, Weight: ptr.To[int32](2)},
|
||||
{Name: names.InterPodAffinity, Weight: ptr.To[int32](2)},
|
||||
{Name: names.DefaultPreemption},
|
||||
{Name: names.NodeResourcesBalancedAllocation, Weight: ptr.To[int32](1)},
|
||||
{Name: names.ImageLocality, Weight: ptr.To[int32](1)},
|
||||
{Name: names.DefaultBinder},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Feature gate PodSchedulingReadiness enabled",
|
||||
features: map[featuregate.Feature]bool{
|
||||
features.PodSchedulingReadiness: true,
|
||||
},
|
||||
wantConfig: &v1.Plugins{
|
||||
MultiPoint: v1.PluginSet{
|
||||
Enabled: []v1.Plugin{
|
||||
{Name: names.PrioritySort},
|
||||
{Name: names.NodeUnschedulable},
|
||||
{Name: names.NodeName},
|
||||
{Name: names.TaintToleration, Weight: ptr.To[int32](3)},
|
||||
{Name: names.NodeAffinity, Weight: ptr.To[int32](2)},
|
||||
{Name: names.NodePorts},
|
||||
{Name: names.NodeResourcesFit, Weight: ptr.To[int32](1)},
|
||||
{Name: names.VolumeRestrictions},
|
||||
{Name: names.EBSLimits},
|
||||
{Name: names.GCEPDLimits},
|
||||
{Name: names.NodeVolumeLimits},
|
||||
{Name: names.AzureDiskLimits},
|
||||
{Name: names.VolumeBinding},
|
||||
{Name: names.VolumeZone},
|
||||
{Name: names.PodTopologySpread, Weight: ptr.To[int32](2)},
|
||||
{Name: names.InterPodAffinity, Weight: ptr.To[int32](2)},
|
||||
{Name: names.DefaultPreemption},
|
||||
{Name: names.NodeResourcesBalancedAllocation, Weight: ptr.To[int32](1)},
|
||||
{Name: names.ImageLocality, Weight: ptr.To[int32](1)},
|
||||
{Name: names.DefaultBinder},
|
||||
{Name: names.SchedulingGates},
|
||||
{Name: names.PrioritySort},
|
||||
{Name: names.NodeUnschedulable},
|
||||
{Name: names.NodeName},
|
||||
{Name: names.TaintToleration, Weight: ptr.To[int32](3)},
|
||||
{Name: names.NodeAffinity, Weight: ptr.To[int32](2)},
|
||||
{Name: names.NodePorts},
|
||||
{Name: names.NodeResourcesFit, Weight: ptr.To[int32](1)},
|
||||
{Name: names.VolumeRestrictions},
|
||||
{Name: names.EBSLimits},
|
||||
{Name: names.GCEPDLimits},
|
||||
{Name: names.NodeVolumeLimits},
|
||||
{Name: names.AzureDiskLimits},
|
||||
{Name: names.VolumeBinding},
|
||||
{Name: names.VolumeZone},
|
||||
{Name: names.PodTopologySpread, Weight: ptr.To[int32](2)},
|
||||
{Name: names.InterPodAffinity, Weight: ptr.To[int32](2)},
|
||||
{Name: names.DefaultPreemption},
|
||||
{Name: names.NodeResourcesBalancedAllocation, Weight: ptr.To[int32](1)},
|
||||
{Name: names.ImageLocality, Weight: ptr.To[int32](1)},
|
||||
{Name: names.DefaultBinder},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -109,6 +77,7 @@ func TestApplyFeatureGates(t *testing.T) {
|
||||
wantConfig: &v1.Plugins{
|
||||
MultiPoint: v1.PluginSet{
|
||||
Enabled: []v1.Plugin{
|
||||
{Name: names.SchedulingGates},
|
||||
{Name: names.PrioritySort},
|
||||
{Name: names.NodeUnschedulable},
|
||||
{Name: names.NodeName},
|
||||
@ -130,7 +99,6 @@ func TestApplyFeatureGates(t *testing.T) {
|
||||
{Name: names.NodeResourcesBalancedAllocation, Weight: ptr.To[int32](1)},
|
||||
{Name: names.ImageLocality, Weight: ptr.To[int32](1)},
|
||||
{Name: names.DefaultBinder},
|
||||
{Name: names.SchedulingGates},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -331,6 +331,7 @@ func TestSchedulerDefaults(t *testing.T) {
|
||||
Plugins: &configv1.Plugins{
|
||||
MultiPoint: configv1.PluginSet{
|
||||
Enabled: []configv1.Plugin{
|
||||
{Name: names.SchedulingGates},
|
||||
{Name: names.PrioritySort},
|
||||
{Name: names.NodeUnschedulable},
|
||||
{Name: names.NodeName},
|
||||
@ -351,7 +352,6 @@ func TestSchedulerDefaults(t *testing.T) {
|
||||
{Name: names.NodeResourcesBalancedAllocation, Weight: ptr.To[int32](1)},
|
||||
{Name: names.ImageLocality, Weight: ptr.To[int32](1)},
|
||||
{Name: names.DefaultBinder},
|
||||
{Name: names.SchedulingGates},
|
||||
},
|
||||
},
|
||||
Bind: configv1.PluginSet{
|
||||
|
@ -25,7 +25,6 @@ type Features struct {
|
||||
EnableMinDomainsInPodTopologySpread bool
|
||||
EnableNodeInclusionPolicyInPodTopologySpread bool
|
||||
EnableMatchLabelKeysInPodTopologySpread bool
|
||||
EnablePodSchedulingReadiness bool
|
||||
EnablePodDisruptionConditions bool
|
||||
EnableInPlacePodVerticalScaling bool
|
||||
EnableSidecarContainers bool
|
||||
|
@ -51,7 +51,6 @@ func NewInTreeRegistry() runtime.Registry {
|
||||
EnableMinDomainsInPodTopologySpread: feature.DefaultFeatureGate.Enabled(features.MinDomainsInPodTopologySpread),
|
||||
EnableNodeInclusionPolicyInPodTopologySpread: feature.DefaultFeatureGate.Enabled(features.NodeInclusionPolicyInPodTopologySpread),
|
||||
EnableMatchLabelKeysInPodTopologySpread: feature.DefaultFeatureGate.Enabled(features.MatchLabelKeysInPodTopologySpread),
|
||||
EnablePodSchedulingReadiness: feature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness),
|
||||
EnablePodDisruptionConditions: feature.DefaultFeatureGate.Enabled(features.PodDisruptionConditions),
|
||||
EnableInPlacePodVerticalScaling: feature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling),
|
||||
EnableSidecarContainers: feature.DefaultFeatureGate.Enabled(features.SidecarContainers),
|
||||
@ -80,7 +79,7 @@ func NewInTreeRegistry() runtime.Registry {
|
||||
queuesort.Name: queuesort.New,
|
||||
defaultbinder.Name: defaultbinder.New,
|
||||
defaultpreemption.Name: runtime.FactoryAdapter(fts, defaultpreemption.New),
|
||||
schedulinggates.Name: runtime.FactoryAdapter(fts, schedulinggates.New),
|
||||
schedulinggates.Name: schedulinggates.New,
|
||||
}
|
||||
|
||||
return registry
|
||||
|
@ -23,7 +23,6 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/names"
|
||||
)
|
||||
|
||||
@ -31,9 +30,7 @@ import (
|
||||
const Name = names.SchedulingGates
|
||||
|
||||
// SchedulingGates checks if a Pod carries .spec.schedulingGates.
|
||||
type SchedulingGates struct {
|
||||
EnablePodSchedulingReadiness bool
|
||||
}
|
||||
type SchedulingGates struct{}
|
||||
|
||||
var _ framework.PreEnqueuePlugin = &SchedulingGates{}
|
||||
var _ framework.EnqueueExtensions = &SchedulingGates{}
|
||||
@ -43,7 +40,7 @@ func (pl *SchedulingGates) Name() string {
|
||||
}
|
||||
|
||||
func (pl *SchedulingGates) PreEnqueue(ctx context.Context, p *v1.Pod) *framework.Status {
|
||||
if !pl.EnablePodSchedulingReadiness || len(p.Spec.SchedulingGates) == 0 {
|
||||
if len(p.Spec.SchedulingGates) == 0 {
|
||||
return nil
|
||||
}
|
||||
gates := make([]string, 0, len(p.Spec.SchedulingGates))
|
||||
@ -60,6 +57,6 @@ func (pl *SchedulingGates) EventsToRegister() []framework.ClusterEventWithHint {
|
||||
}
|
||||
|
||||
// New initializes a new plugin and returns it.
|
||||
func New(_ context.Context, _ runtime.Object, _ framework.Handle, fts feature.Features) (framework.Plugin, error) {
|
||||
return &SchedulingGates{EnablePodSchedulingReadiness: fts.EnablePodSchedulingReadiness}, nil
|
||||
func New(_ context.Context, _ runtime.Object, _ framework.Handle) (framework.Plugin, error) {
|
||||
return &SchedulingGates{}, nil
|
||||
}
|
||||
|
@ -23,48 +23,32 @@ import (
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature"
|
||||
st "k8s.io/kubernetes/pkg/scheduler/testing"
|
||||
"k8s.io/kubernetes/test/utils/ktesting"
|
||||
)
|
||||
|
||||
func TestPreEnqueue(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pod *v1.Pod
|
||||
enablePodSchedulingReadiness bool
|
||||
want *framework.Status
|
||||
name string
|
||||
pod *v1.Pod
|
||||
want *framework.Status
|
||||
}{
|
||||
{
|
||||
name: "pod does not carry scheduling gates, feature disabled",
|
||||
pod: st.MakePod().Name("p").Obj(),
|
||||
enablePodSchedulingReadiness: false,
|
||||
want: nil,
|
||||
name: "pod does not carry scheduling gates",
|
||||
pod: st.MakePod().Name("p").Obj(),
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "pod does not carry scheduling gates, feature enabled",
|
||||
pod: st.MakePod().Name("p").Obj(),
|
||||
enablePodSchedulingReadiness: true,
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "pod carries scheduling gates, feature disabled",
|
||||
pod: st.MakePod().Name("p").SchedulingGates([]string{"foo", "bar"}).Obj(),
|
||||
enablePodSchedulingReadiness: false,
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "pod carries scheduling gates, feature enabled",
|
||||
pod: st.MakePod().Name("p").SchedulingGates([]string{"foo", "bar"}).Obj(),
|
||||
enablePodSchedulingReadiness: true,
|
||||
want: framework.NewStatus(framework.UnschedulableAndUnresolvable, "waiting for scheduling gates: [foo bar]"),
|
||||
name: "pod carries scheduling gates",
|
||||
pod: st.MakePod().Name("p").SchedulingGates([]string{"foo", "bar"}).Obj(),
|
||||
want: framework.NewStatus(framework.UnschedulableAndUnresolvable, "waiting for scheduling gates: [foo bar]"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, ctx := ktesting.NewTestContext(t)
|
||||
p, err := New(ctx, nil, nil, feature.Features{EnablePodSchedulingReadiness: tt.enablePodSchedulingReadiness})
|
||||
p, err := New(ctx, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Creating plugin: %v", err)
|
||||
}
|
||||
|
@ -4194,13 +4194,10 @@ message PodSpec {
|
||||
//
|
||||
// SchedulingGates can only be set at pod creation time, and be removed only afterwards.
|
||||
//
|
||||
// This is a beta feature enabled by the PodSchedulingReadiness feature gate.
|
||||
//
|
||||
// +patchMergeKey=name
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=name
|
||||
// +featureGate=PodSchedulingReadiness
|
||||
// +optional
|
||||
repeated PodSchedulingGate schedulingGates = 38;
|
||||
|
||||
|
@ -3797,13 +3797,10 @@ type PodSpec struct {
|
||||
//
|
||||
// SchedulingGates can only be set at pod creation time, and be removed only afterwards.
|
||||
//
|
||||
// This is a beta feature enabled by the PodSchedulingReadiness feature gate.
|
||||
//
|
||||
// +patchMergeKey=name
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=name
|
||||
// +featureGate=PodSchedulingReadiness
|
||||
// +optional
|
||||
SchedulingGates []PodSchedulingGate `json:"schedulingGates,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,38,opt,name=schedulingGates"`
|
||||
// ResourceClaims defines which ResourceClaims must be allocated
|
||||
|
@ -1759,7 +1759,7 @@ var map_PodSpec = map[string]string{
|
||||
"setHostnameAsFQDN": "If true the pod's hostname will be configured as the pod's FQDN, rather than the leaf name (the default). In Linux containers, this means setting the FQDN in the hostname field of the kernel (the nodename field of struct utsname). In Windows containers, this means setting the registry value of hostname for the registry key HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters to FQDN. If a pod does not have FQDN, this has no effect. Default to false.",
|
||||
"os": "Specifies the OS of the containers in the pod. Some pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset: -securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile - spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls - spec.shareProcessNamespace - spec.securityContext.runAsUser - spec.securityContext.runAsGroup - spec.securityContext.supplementalGroups - spec.containers[*].securityContext.seLinuxOptions - spec.containers[*].securityContext.seccompProfile - spec.containers[*].securityContext.capabilities - spec.containers[*].securityContext.readOnlyRootFilesystem - spec.containers[*].securityContext.privileged - spec.containers[*].securityContext.allowPrivilegeEscalation - spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser - spec.containers[*].securityContext.runAsGroup",
|
||||
"hostUsers": "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host. This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature.",
|
||||
"schedulingGates": "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. If schedulingGates is not empty, the pod will stay in the SchedulingGated state and the scheduler will not attempt to schedule the pod.\n\nSchedulingGates can only be set at pod creation time, and be removed only afterwards.\n\nThis is a beta feature enabled by the PodSchedulingReadiness feature gate.",
|
||||
"schedulingGates": "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. If schedulingGates is not empty, the pod will stay in the SchedulingGated state and the scheduler will not attempt to schedule the pod.\n\nSchedulingGates can only be set at pod creation time, and be removed only afterwards.",
|
||||
"resourceClaims": "ResourceClaims defines which ResourceClaims must be allocated and reserved before the Pod is allowed to start. The resources will be made available to those containers which consume them by name.\n\nThis is an alpha field and requires enabling the DynamicResourceAllocation feature gate.\n\nThis field is immutable.",
|
||||
}
|
||||
|
||||
|
@ -25,12 +25,9 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
typedv1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/test/integration"
|
||||
"k8s.io/kubernetes/test/integration/framework"
|
||||
)
|
||||
@ -689,47 +686,11 @@ func TestMutablePodSchedulingDirectives(t *testing.T) {
|
||||
defer framework.DeleteNamespaceOrDie(client, ns, t)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
create *v1.Pod
|
||||
update *v1.Pod
|
||||
enableSchedulingGates bool
|
||||
err string
|
||||
name string
|
||||
create *v1.Pod
|
||||
update *v1.Pod
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "node selector is immutable when AllowMutableNodeSelector is false",
|
||||
create: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-pod",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "fake-name",
|
||||
Image: "fakeimage",
|
||||
},
|
||||
},
|
||||
SchedulingGates: []v1.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
update: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-pod",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "fake-name",
|
||||
Image: "fakeimage",
|
||||
},
|
||||
},
|
||||
NodeSelector: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
SchedulingGates: []v1.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
|
||||
},
|
||||
{
|
||||
name: "adding node selector is allowed for gated pods",
|
||||
create: &v1.Pod{
|
||||
@ -763,7 +724,6 @@ func TestMutablePodSchedulingDirectives(t *testing.T) {
|
||||
SchedulingGates: []v1.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
enableSchedulingGates: true,
|
||||
},
|
||||
{
|
||||
name: "addition to nodeAffinity is allowed for gated pods",
|
||||
@ -842,7 +802,6 @@ func TestMutablePodSchedulingDirectives(t *testing.T) {
|
||||
SchedulingGates: []v1.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
enableSchedulingGates: true,
|
||||
},
|
||||
{
|
||||
name: "addition to nodeAffinity is allowed for gated pods with nil affinity",
|
||||
@ -899,12 +858,9 @@ func TestMutablePodSchedulingDirectives(t *testing.T) {
|
||||
SchedulingGates: []v1.PodSchedulingGate{{Name: "baz"}},
|
||||
},
|
||||
},
|
||||
enableSchedulingGates: true,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, tc.enableSchedulingGates)()
|
||||
|
||||
if _, err := client.CoreV1().Pods(ns.Name).Create(context.TODO(), tc.create, metav1.CreateOptions{}); err != nil {
|
||||
t.Errorf("Failed to create pod: %v", err)
|
||||
}
|
||||
|
@ -33,12 +33,9 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/util/feature"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
listersv1 "k8s.io/client-go/listers/core/v1"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
configv1 "k8s.io/kube-scheduler/config/v1"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/scheduler"
|
||||
schedulerconfig "k8s.io/kubernetes/pkg/scheduler/apis/config"
|
||||
configtesting "k8s.io/kubernetes/pkg/scheduler/apis/config/testing"
|
||||
@ -2199,8 +2196,6 @@ func TestPreScorePlugin(t *testing.T) {
|
||||
|
||||
// TestPreEnqueuePlugin tests invocation of enqueue plugins.
|
||||
func TestPreEnqueuePlugin(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.PodSchedulingReadiness, true)()
|
||||
|
||||
testContext := testutils.InitTestAPIServer(t, "enqueue-plugin", nil)
|
||||
|
||||
tests := []struct {
|
||||
@ -2636,8 +2631,6 @@ func (pl *SchedulingGatesPluginWOEvents) EventsToRegister() []framework.ClusterE
|
||||
|
||||
// This test helps to verify registering nil events for schedulingGates plugin works as expected.
|
||||
func TestSchedulingGatesPluginEventsToRegister(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.PodSchedulingReadiness, true)()
|
||||
|
||||
testContext := testutils.InitTestAPIServer(t, "preenqueue-plugin", nil)
|
||||
|
||||
num := func(pl framework.Plugin) int {
|
||||
@ -2659,12 +2652,12 @@ func TestSchedulingGatesPluginEventsToRegister(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "preEnqueue plugin without event registered",
|
||||
enqueuePlugin: &SchedulingGatesPluginWOEvents{SchedulingGates: schedulinggates.SchedulingGates{EnablePodSchedulingReadiness: true}},
|
||||
enqueuePlugin: &SchedulingGatesPluginWOEvents{SchedulingGates: schedulinggates.SchedulingGates{}},
|
||||
count: 2,
|
||||
},
|
||||
{
|
||||
name: "preEnqueue plugin with event registered",
|
||||
enqueuePlugin: &SchedulingGatesPluginWithEvents{SchedulingGates: schedulinggates.SchedulingGates{EnablePodSchedulingReadiness: true}},
|
||||
enqueuePlugin: &SchedulingGatesPluginWithEvents{SchedulingGates: schedulinggates.SchedulingGates{}},
|
||||
count: 3,
|
||||
},
|
||||
}
|
||||
|
@ -58,55 +58,33 @@ func TestSchedulingGates(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pods []*v1.Pod
|
||||
featureEnabled bool
|
||||
want []string
|
||||
rmPodsSchedulingGates []int
|
||||
wantPostGatesRemoval []string
|
||||
}{
|
||||
{
|
||||
name: "feature disabled, regular pods",
|
||||
name: "regular pods",
|
||||
pods: []*v1.Pod{
|
||||
st.MakePod().Name("p1").Container("pause").Obj(),
|
||||
st.MakePod().Name("p2").Container("pause").Obj(),
|
||||
},
|
||||
featureEnabled: false,
|
||||
want: []string{"p1", "p2"},
|
||||
want: []string{"p1", "p2"},
|
||||
},
|
||||
{
|
||||
name: "feature enabled, regular pods",
|
||||
pods: []*v1.Pod{
|
||||
st.MakePod().Name("p1").Container("pause").Obj(),
|
||||
st.MakePod().Name("p2").Container("pause").Obj(),
|
||||
},
|
||||
featureEnabled: true,
|
||||
want: []string{"p1", "p2"},
|
||||
},
|
||||
{
|
||||
name: "feature disabled, one pod carrying scheduling gates",
|
||||
name: "one pod carrying scheduling gates",
|
||||
pods: []*v1.Pod{
|
||||
st.MakePod().Name("p1").SchedulingGates([]string{"foo"}).Container("pause").Obj(),
|
||||
st.MakePod().Name("p2").Container("pause").Obj(),
|
||||
},
|
||||
featureEnabled: false,
|
||||
want: []string{"p1", "p2"},
|
||||
want: []string{"p2"},
|
||||
},
|
||||
{
|
||||
name: "feature enabled, one pod carrying scheduling gates",
|
||||
pods: []*v1.Pod{
|
||||
st.MakePod().Name("p1").SchedulingGates([]string{"foo"}).Container("pause").Obj(),
|
||||
st.MakePod().Name("p2").Container("pause").Obj(),
|
||||
},
|
||||
featureEnabled: true,
|
||||
want: []string{"p2"},
|
||||
},
|
||||
{
|
||||
name: "feature enabled, two pod carrying scheduling gates, and remove gates of one pod",
|
||||
name: "two pod carrying scheduling gates, and remove gates of one pod",
|
||||
pods: []*v1.Pod{
|
||||
st.MakePod().Name("p1").SchedulingGates([]string{"foo"}).Container("pause").Obj(),
|
||||
st.MakePod().Name("p2").SchedulingGates([]string{"bar"}).Container("pause").Obj(),
|
||||
st.MakePod().Name("p3").Container("pause").Obj(),
|
||||
},
|
||||
featureEnabled: true,
|
||||
want: []string{"p3"},
|
||||
rmPodsSchedulingGates: []int{1}, // remove gates of 'p2'
|
||||
wantPostGatesRemoval: []string{"p2"},
|
||||
@ -115,8 +93,6 @@ func TestSchedulingGates(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, tt.featureEnabled)()
|
||||
|
||||
// Use zero backoff seconds to bypass backoffQ.
|
||||
// It's intended to not start the scheduler's queue, and hence to
|
||||
// not start any flushing logic. We will pop and schedule the Pods manually later.
|
||||
|
Loading…
Reference in New Issue
Block a user