mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 19:01:49 +00:00
Merge pull request #113274 from Huang-Wei/kep-3521-A
[KEP-3521] Part 1: New Pod API .spec.schedulingGates
This commit is contained in:
commit
8c77820759
26
api/openapi-spec/swagger.json
generated
26
api/openapi-spec/swagger.json
generated
@ -7248,6 +7248,19 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.PodSchedulingGate": {
|
||||
"description": "PodSchedulingGate is associated to a Pod to guard its scheduling.",
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name of the scheduling gate. Each scheduling gate must have a unique name field.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.PodSecurityContext": {
|
||||
"description": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.",
|
||||
"properties": {
|
||||
@ -7452,6 +7465,19 @@
|
||||
"description": "If specified, the pod will be dispatched by specified scheduler. If not specified, the pod will be dispatched by default scheduler.",
|
||||
"type": "string"
|
||||
},
|
||||
"schedulingGates": {
|
||||
"description": "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.\n\nThis is an alpha-level feature enabled by PodSchedulingReadiness feature gate.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/io.k8s.api.core.v1.PodSchedulingGate"
|
||||
},
|
||||
"type": "array",
|
||||
"x-kubernetes-list-map-keys": [
|
||||
"name"
|
||||
],
|
||||
"x-kubernetes-list-type": "map",
|
||||
"x-kubernetes-patch-merge-key": "name",
|
||||
"x-kubernetes-patch-strategy": "merge"
|
||||
},
|
||||
"securityContext": {
|
||||
"$ref": "#/definitions/io.k8s.api.core.v1.PodSecurityContext",
|
||||
"description": "SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field."
|
||||
|
@ -4869,6 +4869,20 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.PodSchedulingGate": {
|
||||
"description": "PodSchedulingGate is associated to a Pod to guard its scheduling.",
|
||||
"properties": {
|
||||
"name": {
|
||||
"default": "",
|
||||
"description": "Name of the scheduling gate. Each scheduling gate must have a unique name field.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.PodSecurityContext": {
|
||||
"description": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.",
|
||||
"properties": {
|
||||
@ -5139,6 +5153,24 @@
|
||||
"description": "If specified, the pod will be dispatched by specified scheduler. If not specified, the pod will be dispatched by default scheduler.",
|
||||
"type": "string"
|
||||
},
|
||||
"schedulingGates": {
|
||||
"description": "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.\n\nThis is an alpha-level feature enabled by PodSchedulingReadiness feature gate.",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/io.k8s.api.core.v1.PodSchedulingGate"
|
||||
}
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"type": "array",
|
||||
"x-kubernetes-list-map-keys": [
|
||||
"name"
|
||||
],
|
||||
"x-kubernetes-list-type": "map",
|
||||
"x-kubernetes-patch-merge-key": "name",
|
||||
"x-kubernetes-patch-strategy": "merge"
|
||||
},
|
||||
"securityContext": {
|
||||
"allOf": [
|
||||
{
|
||||
|
@ -3291,6 +3291,20 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.PodSchedulingGate": {
|
||||
"description": "PodSchedulingGate is associated to a Pod to guard its scheduling.",
|
||||
"properties": {
|
||||
"name": {
|
||||
"default": "",
|
||||
"description": "Name of the scheduling gate. Each scheduling gate must have a unique name field.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.PodSecurityContext": {
|
||||
"description": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.",
|
||||
"properties": {
|
||||
@ -3561,6 +3575,24 @@
|
||||
"description": "If specified, the pod will be dispatched by specified scheduler. If not specified, the pod will be dispatched by default scheduler.",
|
||||
"type": "string"
|
||||
},
|
||||
"schedulingGates": {
|
||||
"description": "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.\n\nThis is an alpha-level feature enabled by PodSchedulingReadiness feature gate.",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/io.k8s.api.core.v1.PodSchedulingGate"
|
||||
}
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"type": "array",
|
||||
"x-kubernetes-list-map-keys": [
|
||||
"name"
|
||||
],
|
||||
"x-kubernetes-list-type": "map",
|
||||
"x-kubernetes-patch-merge-key": "name",
|
||||
"x-kubernetes-patch-strategy": "merge"
|
||||
},
|
||||
"securityContext": {
|
||||
"allOf": [
|
||||
{
|
||||
|
@ -2485,6 +2485,20 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.PodSchedulingGate": {
|
||||
"description": "PodSchedulingGate is associated to a Pod to guard its scheduling.",
|
||||
"properties": {
|
||||
"name": {
|
||||
"default": "",
|
||||
"description": "Name of the scheduling gate. Each scheduling gate must have a unique name field.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.PodSecurityContext": {
|
||||
"description": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.",
|
||||
"properties": {
|
||||
@ -2755,6 +2769,24 @@
|
||||
"description": "If specified, the pod will be dispatched by specified scheduler. If not specified, the pod will be dispatched by default scheduler.",
|
||||
"type": "string"
|
||||
},
|
||||
"schedulingGates": {
|
||||
"description": "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.\n\nThis is an alpha-level feature enabled by PodSchedulingReadiness feature gate.",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/io.k8s.api.core.v1.PodSchedulingGate"
|
||||
}
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"type": "array",
|
||||
"x-kubernetes-list-map-keys": [
|
||||
"name"
|
||||
],
|
||||
"x-kubernetes-list-type": "map",
|
||||
"x-kubernetes-patch-merge-key": "name",
|
||||
"x-kubernetes-patch-strategy": "merge"
|
||||
},
|
||||
"securityContext": {
|
||||
"allOf": [
|
||||
{
|
||||
|
@ -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)
|
||||
|
||||
dropDisabledTopologySpreadConstraintsFields(podSpec, oldPodSpec)
|
||||
@ -719,6 +724,14 @@ func probeGracePeriodInUse(podSpec *api.PodSpec) bool {
|
||||
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
|
||||
// converted annotation value
|
||||
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
|
||||
// can't schedule the pod right now, for example due to insufficient resources in the cluster.
|
||||
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 PodConditionType = "ContainersReady"
|
||||
// AlphaNoCompatGuaranteeDisruptionTarget indicates the pod is about to be deleted due to a
|
||||
@ -2997,6 +3000,12 @@ type PodSpec struct {
|
||||
// - spec.containers[*].securityContext.runAsGroup
|
||||
// +optional
|
||||
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.
|
||||
@ -3017,6 +3026,13 @@ type PodOS struct {
|
||||
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
|
||||
// pod's hosts file.
|
||||
type HostAlias struct {
|
||||
|
32
pkg/apis/core/v1/zz_generated.conversion.go
generated
32
pkg/apis/core/v1/zz_generated.conversion.go
generated
@ -1352,6 +1352,16 @@ func RegisterConversions(s *runtime.Scheme) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*v1.PodSchedulingGate)(nil), (*core.PodSchedulingGate)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_PodSchedulingGate_To_core_PodSchedulingGate(a.(*v1.PodSchedulingGate), b.(*core.PodSchedulingGate), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*core.PodSchedulingGate)(nil), (*v1.PodSchedulingGate)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_core_PodSchedulingGate_To_v1_PodSchedulingGate(a.(*core.PodSchedulingGate), b.(*v1.PodSchedulingGate), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*v1.PodSecurityContext)(nil), (*core.PodSecurityContext)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_PodSecurityContext_To_core_PodSecurityContext(a.(*v1.PodSecurityContext), b.(*core.PodSecurityContext), scope)
|
||||
}); err != nil {
|
||||
@ -6069,6 +6079,26 @@ func Convert_core_PodReadinessGate_To_v1_PodReadinessGate(in *core.PodReadinessG
|
||||
return autoConvert_core_PodReadinessGate_To_v1_PodReadinessGate(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_PodSchedulingGate_To_core_PodSchedulingGate(in *v1.PodSchedulingGate, out *core.PodSchedulingGate, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_PodSchedulingGate_To_core_PodSchedulingGate is an autogenerated conversion function.
|
||||
func Convert_v1_PodSchedulingGate_To_core_PodSchedulingGate(in *v1.PodSchedulingGate, out *core.PodSchedulingGate, s conversion.Scope) error {
|
||||
return autoConvert_v1_PodSchedulingGate_To_core_PodSchedulingGate(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_core_PodSchedulingGate_To_v1_PodSchedulingGate(in *core.PodSchedulingGate, out *v1.PodSchedulingGate, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_core_PodSchedulingGate_To_v1_PodSchedulingGate is an autogenerated conversion function.
|
||||
func Convert_core_PodSchedulingGate_To_v1_PodSchedulingGate(in *core.PodSchedulingGate, out *v1.PodSchedulingGate, s conversion.Scope) error {
|
||||
return autoConvert_core_PodSchedulingGate_To_v1_PodSchedulingGate(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_PodSecurityContext_To_core_PodSecurityContext(in *v1.PodSecurityContext, out *core.PodSecurityContext, s conversion.Scope) error {
|
||||
out.SELinuxOptions = (*core.SELinuxOptions)(unsafe.Pointer(in.SELinuxOptions))
|
||||
out.WindowsOptions = (*core.WindowsSecurityContextOptions)(unsafe.Pointer(in.WindowsOptions))
|
||||
@ -6188,6 +6218,7 @@ func autoConvert_v1_PodSpec_To_core_PodSpec(in *v1.PodSpec, out *core.PodSpec, s
|
||||
out.SetHostnameAsFQDN = (*bool)(unsafe.Pointer(in.SetHostnameAsFQDN))
|
||||
out.OS = (*core.PodOS)(unsafe.Pointer(in.OS))
|
||||
// INFO: in.HostUsers opted out of conversion generation
|
||||
out.SchedulingGates = *(*[]core.PodSchedulingGate)(unsafe.Pointer(&in.SchedulingGates))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -6241,6 +6272,7 @@ func autoConvert_core_PodSpec_To_v1_PodSpec(in *core.PodSpec, out *v1.PodSpec, s
|
||||
out.EnableServiceLinks = (*bool)(unsafe.Pointer(in.EnableServiceLinks))
|
||||
out.TopologySpreadConstraints = *(*[]v1.TopologySpreadConstraint)(unsafe.Pointer(&in.TopologySpreadConstraints))
|
||||
out.OS = (*v1.PodOS)(unsafe.Pointer(in.OS))
|
||||
out.SchedulingGates = *(*[]v1.PodSchedulingGate)(unsafe.Pointer(&in.SchedulingGates))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -3272,6 +3272,22 @@ func validateReadinessGates(readinessGates []core.PodReadinessGate, fldPath *fie
|
||||
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 {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
@ -3420,6 +3436,28 @@ func validateOnlyAddedTolerations(newTolerations []core.Toleration, oldToleratio
|
||||
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 {
|
||||
allErrs := field.ErrorList{}
|
||||
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, validatePodDNSConfig(spec.DNSConfig, &spec.DNSPolicy, fldPath.Child("dnsConfig"), opts)...)
|
||||
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, validateWindowsHostProcessPod(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 {
|
||||
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)...)
|
||||
|
||||
return allErrs
|
||||
@ -4370,6 +4414,7 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
|
||||
// 2. spec.initContainers[*].image
|
||||
// 3. spec.activeDeadlineSeconds
|
||||
// 4. spec.terminationGracePeriodSeconds
|
||||
// 5. spec.schedulingGates
|
||||
|
||||
containerErrs, stop := ValidateContainerUpdates(newPod.Spec.Containers, oldPod.Spec.Containers, specPath.Child("containers"))
|
||||
allErrs = append(allErrs, containerErrs...)
|
||||
@ -4405,6 +4450,9 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
|
||||
// Allow only additions to tolerations updates.
|
||||
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
|
||||
// so far and save the cost of a deep copy.
|
||||
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
|
||||
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
|
||||
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) {
|
||||
var (
|
||||
activeDeadlineSecondsZero = int64(0)
|
||||
@ -11698,6 +11783,54 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
err: "Forbidden: pod updates may not change fields other than ",
|
||||
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 {
|
||||
test.new.ObjectMeta.ResourceVersion = "1"
|
||||
@ -18481,6 +18614,7 @@ func TestValidateOSFields(t *testing.T) {
|
||||
"RestartPolicy",
|
||||
"RuntimeClassName",
|
||||
"SchedulerName",
|
||||
"SchedulingGates[*].Name",
|
||||
"SecurityContext.RunAsNonRoot",
|
||||
"ServiceAccountName",
|
||||
"SetHostnameAsFQDN",
|
||||
@ -18517,6 +18651,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.
|
||||
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) {
|
||||
|
21
pkg/apis/core/zz_generated.deepcopy.go
generated
21
pkg/apis/core/zz_generated.deepcopy.go
generated
@ -3728,6 +3728,22 @@ func (in *PodReadinessGate) DeepCopy() *PodReadinessGate {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PodSchedulingGate) DeepCopyInto(out *PodSchedulingGate) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSchedulingGate.
|
||||
func (in *PodSchedulingGate) DeepCopy() *PodSchedulingGate {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PodSchedulingGate)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PodSecurityContext) DeepCopyInto(out *PodSecurityContext) {
|
||||
*out = *in
|
||||
@ -3961,6 +3977,11 @@ func (in *PodSpec) DeepCopyInto(out *PodSpec) {
|
||||
*out = new(PodOS)
|
||||
**out = **in
|
||||
}
|
||||
if in.SchedulingGates != nil {
|
||||
in, out := &in.SchedulingGates, &out.SchedulingGates
|
||||
*out = make([]PodSchedulingGate, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -643,6 +643,13 @@ const (
|
||||
// sandbox creation and network configuration completes successfully
|
||||
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
|
||||
// alpha: v1.22
|
||||
// beta: v1.23
|
||||
@ -996,6 +1003,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
||||
|
||||
PodHasNetworkCondition: {Default: false, PreRelease: featuregate.Alpha},
|
||||
|
||||
PodSchedulingReadiness: {Default: false, PreRelease: featuregate.Alpha},
|
||||
|
||||
PodSecurity: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||
|
||||
ProbeTerminationGracePeriod: {Default: true, PreRelease: featuregate.Beta}, // Default to true in beta 1.25
|
||||
|
49
pkg/generated/openapi/zz_generated.openapi.go
generated
49
pkg/generated/openapi/zz_generated.openapi.go
generated
@ -448,6 +448,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
"k8s.io/api/core/v1.PodPortForwardOptions": schema_k8sio_api_core_v1_PodPortForwardOptions(ref),
|
||||
"k8s.io/api/core/v1.PodProxyOptions": schema_k8sio_api_core_v1_PodProxyOptions(ref),
|
||||
"k8s.io/api/core/v1.PodReadinessGate": schema_k8sio_api_core_v1_PodReadinessGate(ref),
|
||||
"k8s.io/api/core/v1.PodSchedulingGate": schema_k8sio_api_core_v1_PodSchedulingGate(ref),
|
||||
"k8s.io/api/core/v1.PodSecurityContext": schema_k8sio_api_core_v1_PodSecurityContext(ref),
|
||||
"k8s.io/api/core/v1.PodSignature": schema_k8sio_api_core_v1_PodSignature(ref),
|
||||
"k8s.io/api/core/v1.PodSpec": schema_k8sio_api_core_v1_PodSpec(ref),
|
||||
@ -22346,6 +22347,28 @@ func schema_k8sio_api_core_v1_PodReadinessGate(ref common.ReferenceCallback) com
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_core_v1_PodSchedulingGate(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "PodSchedulingGate is associated to a Pod to guard its scheduling.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"name": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Name of the scheduling gate. Each scheduling gate must have a unique name field.",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"name"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_core_v1_PodSecurityContext(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
@ -22858,12 +22881,36 @@ func schema_k8sio_api_core_v1_PodSpec(ref common.ReferenceCallback) common.OpenA
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"schedulingGates": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-map-keys": []interface{}{
|
||||
"name",
|
||||
},
|
||||
"x-kubernetes-list-type": "map",
|
||||
"x-kubernetes-patch-merge-key": "name",
|
||||
"x-kubernetes-patch-strategy": "merge",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "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.\n\nThis is an alpha-level feature enabled by PodSchedulingReadiness feature gate.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/core/v1.PodSchedulingGate"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"containers"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.EphemeralContainer", "k8s.io/api/core/v1.HostAlias", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodDNSConfig", "k8s.io/api/core/v1.PodOS", "k8s.io/api/core/v1.PodReadinessGate", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.TopologySpreadConstraint", "k8s.io/api/core/v1.Volume", "k8s.io/apimachinery/pkg/api/resource.Quantity"},
|
||||
"k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.EphemeralContainer", "k8s.io/api/core/v1.HostAlias", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodDNSConfig", "k8s.io/api/core/v1.PodOS", "k8s.io/api/core/v1.PodReadinessGate", "k8s.io/api/core/v1.PodSchedulingGate", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.TopologySpreadConstraint", "k8s.io/api/core/v1.Volume", "k8s.io/apimachinery/pkg/api/resource.Quantity"},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -767,6 +767,13 @@ func printPod(pod *api.Pod, options printers.GenerateOptions) ([]metav1.TableRow
|
||||
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{
|
||||
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>"}}},
|
||||
},
|
||||
{
|
||||
// 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 {
|
||||
|
@ -33,10 +33,12 @@ import (
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
storeerr "k8s.io/apiserver/pkg/storage/errors"
|
||||
"k8s.io/apiserver/pkg/util/dryrun"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
policyclient "k8s.io/client-go/kubernetes/typed/policy/v1"
|
||||
podutil "k8s.io/kubernetes/pkg/api/pod"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/kubelet/client"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
||||
@ -221,6 +223,10 @@ func (r *BindingREST) setPodHostAndAnnotations(ctx context.Context, podUID types
|
||||
if 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
|
||||
if pod.Annotations == nil {
|
||||
pod.Annotations = make(map[string]string)
|
||||
|
@ -18,6 +18,7 @@ package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
@ -41,7 +42,10 @@ 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"
|
||||
)
|
||||
@ -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 {
|
||||
return &api.Binding{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
|
@ -38,12 +38,14 @@ import (
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
podutil "k8s.io/kubernetes/pkg/api/pod"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/helper/qos"
|
||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/kubelet/client"
|
||||
proxyutil "k8s.io/kubernetes/pkg/proxy/util"
|
||||
"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)
|
||||
|
||||
applySeccompVersionSkew(pod)
|
||||
applyWaitingForSchedulingGatesCondition(pod)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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:
|
||||
// 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
|
||||
|
@ -35,9 +35,12 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
apitesting "k8s.io/kubernetes/pkg/api/testing"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/kubelet/client"
|
||||
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) {
|
||||
defaultGracePeriod := int64(30)
|
||||
tcs := []struct {
|
||||
|
2174
staging/src/k8s.io/api/core/v1/generated.pb.go
generated
2174
staging/src/k8s.io/api/core/v1/generated.pb.go
generated
File diff suppressed because it is too large
Load Diff
@ -3353,6 +3353,13 @@ message PodReadinessGate {
|
||||
optional string conditionType = 1;
|
||||
}
|
||||
|
||||
// PodSchedulingGate is associated to a Pod to guard its scheduling.
|
||||
message PodSchedulingGate {
|
||||
// Name of the scheduling gate.
|
||||
// Each scheduling gate must have a unique name field.
|
||||
optional string name = 1;
|
||||
}
|
||||
|
||||
// PodSecurityContext holds pod-level security attributes and common container settings.
|
||||
// Some fields are also present in container.securityContext. Field values of
|
||||
// container.securityContext take precedence over field values of PodSecurityContext.
|
||||
@ -3747,6 +3754,17 @@ message PodSpec {
|
||||
// +k8s:conversion-gen=false
|
||||
// +optional
|
||||
optional bool hostUsers = 37;
|
||||
|
||||
// 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
|
||||
repeated PodSchedulingGate schedulingGates = 38;
|
||||
}
|
||||
|
||||
// PodStatus represents information about the status of a pod. Status may trail the actual
|
||||
|
@ -2666,6 +2666,10 @@ const (
|
||||
// can't schedule the pod right now, for example due to insufficient resources in the cluster.
|
||||
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
|
||||
// during scheduling, for example due to nodeAffinity parsing errors.
|
||||
PodReasonSchedulerError = "SchedulerError"
|
||||
@ -3327,6 +3331,16 @@ type PodSpec struct {
|
||||
// +k8s:conversion-gen=false
|
||||
// +optional
|
||||
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.
|
||||
@ -3347,6 +3361,13 @@ type PodOS struct {
|
||||
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
|
||||
type UnsatisfiableConstraintAction string
|
||||
|
||||
|
@ -1606,6 +1606,15 @@ func (PodReadinessGate) SwaggerDoc() map[string]string {
|
||||
return map_PodReadinessGate
|
||||
}
|
||||
|
||||
var map_PodSchedulingGate = map[string]string{
|
||||
"": "PodSchedulingGate is associated to a Pod to guard its scheduling.",
|
||||
"name": "Name of the scheduling gate. Each scheduling gate must have a unique name field.",
|
||||
}
|
||||
|
||||
func (PodSchedulingGate) SwaggerDoc() map[string]string {
|
||||
return map_PodSchedulingGate
|
||||
}
|
||||
|
||||
var map_PodSecurityContext = map[string]string{
|
||||
"": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.",
|
||||
"seLinuxOptions": "The SELinux context to be applied to all containers. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. Note that this field cannot be set when spec.os.name is windows.",
|
||||
@ -1672,6 +1681,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. More info: https://git.k8s.io/enhancements/keps/sig-scheduling/3521-pod-scheduling-readiness.\n\nThis is an alpha-level feature enabled by PodSchedulingReadiness feature gate.",
|
||||
}
|
||||
|
||||
func (PodSpec) SwaggerDoc() map[string]string {
|
||||
|
@ -3726,6 +3726,22 @@ func (in *PodReadinessGate) DeepCopy() *PodReadinessGate {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PodSchedulingGate) DeepCopyInto(out *PodSchedulingGate) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSchedulingGate.
|
||||
func (in *PodSchedulingGate) DeepCopy() *PodSchedulingGate {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PodSchedulingGate)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PodSecurityContext) DeepCopyInto(out *PodSecurityContext) {
|
||||
*out = *in
|
||||
@ -3959,6 +3975,11 @@ func (in *PodSpec) DeepCopyInto(out *PodSpec) {
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.SchedulingGates != nil {
|
||||
in, out := &in.SchedulingGates, &out.SchedulingGates
|
||||
*out = make([]PodSchedulingGate, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1626,7 +1626,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"updateStrategy": {
|
||||
|
Binary file not shown.
@ -802,6 +802,8 @@ spec:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -1627,7 +1627,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"strategy": {
|
||||
|
Binary file not shown.
@ -810,6 +810,8 @@ spec:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -1628,7 +1628,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
Binary file not shown.
@ -802,6 +802,8 @@ spec:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -1627,7 +1627,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"volumeClaimTemplates": [
|
||||
|
Binary file not shown.
@ -808,6 +808,8 @@ spec:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -1627,7 +1627,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"strategy": {
|
||||
|
Binary file not shown.
@ -812,6 +812,8 @@ spec:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -1627,7 +1627,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"volumeClaimTemplates": [
|
||||
|
Binary file not shown.
@ -808,6 +808,8 @@ spec:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -1626,7 +1626,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"updateStrategy": {
|
||||
|
Binary file not shown.
@ -802,6 +802,8 @@ spec:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -1627,7 +1627,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"strategy": {
|
||||
|
Binary file not shown.
@ -810,6 +810,8 @@ spec:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -1628,7 +1628,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
Binary file not shown.
@ -802,6 +802,8 @@ spec:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -1627,7 +1627,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"volumeClaimTemplates": [
|
||||
|
Binary file not shown.
@ -808,6 +808,8 @@ spec:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -1700,7 +1700,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ttlSecondsAfterFinished": 8,
|
||||
|
Binary file not shown.
@ -854,6 +854,8 @@ spec:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -1651,7 +1651,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ttlSecondsAfterFinished": 8,
|
||||
|
BIN
staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.pb
vendored
BIN
staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.pb
vendored
Binary file not shown.
@ -818,6 +818,8 @@ spec:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -1700,7 +1700,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ttlSecondsAfterFinished": 8,
|
||||
|
Binary file not shown.
@ -854,6 +854,8 @@ spec:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -1694,7 +1694,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ttlSecondsAfterFinished": 8,
|
||||
|
Binary file not shown.
@ -851,6 +851,8 @@ template:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -1568,7 +1568,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"phase": "phaseValue",
|
||||
|
BIN
staging/src/k8s.io/api/testdata/HEAD/core.v1.Pod.pb
vendored
BIN
staging/src/k8s.io/api/testdata/HEAD/core.v1.Pod.pb
vendored
Binary file not shown.
@ -758,6 +758,8 @@ spec:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -1611,7 +1611,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
@ -791,6 +791,8 @@ template:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -1617,7 +1617,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
Binary file not shown.
@ -796,6 +796,8 @@ spec:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -1626,7 +1626,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"updateStrategy": {
|
||||
|
Binary file not shown.
@ -802,6 +802,8 @@ spec:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -1627,7 +1627,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"strategy": {
|
||||
|
Binary file not shown.
@ -812,6 +812,8 @@ spec:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -1628,7 +1628,12 @@
|
||||
"os": {
|
||||
"name": "nameValue"
|
||||
},
|
||||
"hostUsers": true
|
||||
"hostUsers": true,
|
||||
"schedulingGates": [
|
||||
{
|
||||
"name": "nameValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
Binary file not shown.
@ -802,6 +802,8 @@ spec:
|
||||
restartPolicy: restartPolicyValue
|
||||
runtimeClassName: runtimeClassNameValue
|
||||
schedulerName: schedulerNameValue
|
||||
schedulingGates:
|
||||
- name: nameValue
|
||||
securityContext:
|
||||
fsGroup: 5
|
||||
fsGroupChangePolicy: fsGroupChangePolicyValue
|
||||
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
// PodSchedulingGateApplyConfiguration represents an declarative configuration of the PodSchedulingGate type for use
|
||||
// with apply.
|
||||
type PodSchedulingGateApplyConfiguration struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// PodSchedulingGateApplyConfiguration constructs an declarative configuration of the PodSchedulingGate type for use with
|
||||
// apply.
|
||||
func PodSchedulingGate() *PodSchedulingGateApplyConfiguration {
|
||||
return &PodSchedulingGateApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithName sets the Name field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Name field is set to the value of the last call.
|
||||
func (b *PodSchedulingGateApplyConfiguration) WithName(value string) *PodSchedulingGateApplyConfiguration {
|
||||
b.Name = &value
|
||||
return b
|
||||
}
|
@ -62,6 +62,7 @@ type PodSpecApplyConfiguration struct {
|
||||
SetHostnameAsFQDN *bool `json:"setHostnameAsFQDN,omitempty"`
|
||||
OS *PodOSApplyConfiguration `json:"os,omitempty"`
|
||||
HostUsers *bool `json:"hostUsers,omitempty"`
|
||||
SchedulingGates []PodSchedulingGateApplyConfiguration `json:"schedulingGates,omitempty"`
|
||||
}
|
||||
|
||||
// PodSpecApplyConfiguration constructs an declarative configuration of the PodSpec type for use with
|
||||
@ -416,3 +417,16 @@ func (b *PodSpecApplyConfiguration) WithHostUsers(value bool) *PodSpecApplyConfi
|
||||
b.HostUsers = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithSchedulingGates adds the given value to the SchedulingGates field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the SchedulingGates field.
|
||||
func (b *PodSpecApplyConfiguration) WithSchedulingGates(values ...*PodSchedulingGateApplyConfiguration) *PodSpecApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithSchedulingGates")
|
||||
}
|
||||
b.SchedulingGates = append(b.SchedulingGates, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
@ -5725,6 +5725,13 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: io.k8s.api.core.v1.PodSchedulingGate
|
||||
map:
|
||||
fields:
|
||||
- name: name
|
||||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: io.k8s.api.core.v1.PodSecurityContext
|
||||
map:
|
||||
fields:
|
||||
@ -5881,6 +5888,14 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
- name: schedulerName
|
||||
type:
|
||||
scalar: string
|
||||
- name: schedulingGates
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.api.core.v1.PodSchedulingGate
|
||||
elementRelationship: associative
|
||||
keys:
|
||||
- name
|
||||
- name: securityContext
|
||||
type:
|
||||
namedType: io.k8s.api.core.v1.PodSecurityContext
|
||||
|
@ -735,6 +735,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
|
||||
return &applyconfigurationscorev1.PodOSApplyConfiguration{}
|
||||
case corev1.SchemeGroupVersion.WithKind("PodReadinessGate"):
|
||||
return &applyconfigurationscorev1.PodReadinessGateApplyConfiguration{}
|
||||
case corev1.SchemeGroupVersion.WithKind("PodSchedulingGate"):
|
||||
return &applyconfigurationscorev1.PodSchedulingGateApplyConfiguration{}
|
||||
case corev1.SchemeGroupVersion.WithKind("PodSecurityContext"):
|
||||
return &applyconfigurationscorev1.PodSecurityContextApplyConfiguration{}
|
||||
case corev1.SchemeGroupVersion.WithKind("PodSpec"):
|
||||
|
@ -184,6 +184,7 @@ INFO: Unexpected error: wait for pod pending-pod running:
|
||||
SetHostnameAsFQDN: nil,
|
||||
OS: nil,
|
||||
HostUsers: nil,
|
||||
SchedulingGates: nil,
|
||||
},
|
||||
Status: {
|
||||
Phase: "",
|
||||
|
Loading…
Reference in New Issue
Block a user