mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-20 18:31:15 +00:00
APIs, Validation and condition enforcements
- New API field .spec.schedulingGates - Validation and drop disabled fields - Disallow binding a Pod carrying non-nil schedulingGates - Disallow creating a Pod with non-nil nodeName and non-nil schedulingGates - Adds a {type:PodScheduled, reason:WaitingForGates} condition if necessary - New literal SchedulingGated in the STATUS column of `k get pod`
This commit is contained in:
parent
d62cc3dc6d
commit
7b6293b6b6
@ -537,6 +537,11 @@ func dropDisabledFields(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the feature is disabled and not in use, drop the schedulingGates field.
|
||||||
|
if !utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness) && !schedulingGatesInUse(oldPodSpec) {
|
||||||
|
podSpec.SchedulingGates = nil
|
||||||
|
}
|
||||||
|
|
||||||
dropDisabledProcMountField(podSpec, oldPodSpec)
|
dropDisabledProcMountField(podSpec, oldPodSpec)
|
||||||
|
|
||||||
dropDisabledTopologySpreadConstraintsFields(podSpec, oldPodSpec)
|
dropDisabledTopologySpreadConstraintsFields(podSpec, oldPodSpec)
|
||||||
@ -719,6 +724,14 @@ func probeGracePeriodInUse(podSpec *api.PodSpec) bool {
|
|||||||
return inUse
|
return inUse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// schedulingGatesInUse returns true if the pod spec is non-nil and it has SchedulingGates field set.
|
||||||
|
func schedulingGatesInUse(podSpec *api.PodSpec) bool {
|
||||||
|
if podSpec == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return len(podSpec.SchedulingGates) != 0
|
||||||
|
}
|
||||||
|
|
||||||
// SeccompAnnotationForField takes a pod seccomp profile field and returns the
|
// SeccompAnnotationForField takes a pod seccomp profile field and returns the
|
||||||
// converted annotation value
|
// converted annotation value
|
||||||
func SeccompAnnotationForField(field *api.SeccompProfile) string {
|
func SeccompAnnotationForField(field *api.SeccompProfile) string {
|
||||||
|
@ -1935,3 +1935,85 @@ func TestDropHostUsers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDropSchedulingGates(t *testing.T) {
|
||||||
|
podWithSchedulingGates := func() *api.Pod {
|
||||||
|
return &api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
SchedulingGates: []api.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
{Name: "bar"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
podWithoutSchedulingGates := func() *api.Pod { return &api.Pod{} }
|
||||||
|
|
||||||
|
podInfo := []struct {
|
||||||
|
description string
|
||||||
|
hasSchedulingGatesField bool
|
||||||
|
pod func() *api.Pod
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "has SchedulingGates field",
|
||||||
|
hasSchedulingGatesField: true,
|
||||||
|
pod: podWithSchedulingGates,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "does not have SchedulingGates field",
|
||||||
|
hasSchedulingGatesField: false,
|
||||||
|
pod: podWithoutSchedulingGates,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "is nil",
|
||||||
|
hasSchedulingGatesField: false,
|
||||||
|
pod: func() *api.Pod { return nil },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, enabled := range []bool{true, false} {
|
||||||
|
for _, oldPodInfo := range podInfo {
|
||||||
|
for _, newPodInfo := range podInfo {
|
||||||
|
oldPodHasSchedulingGates, oldPod := oldPodInfo.hasSchedulingGatesField, oldPodInfo.pod()
|
||||||
|
newPodHasSchedulingGates, newPod := newPodInfo.hasSchedulingGatesField, newPodInfo.pod()
|
||||||
|
if newPod == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(fmt.Sprintf("feature enabled=%v, old pod %v, new pod %v", enabled, oldPodInfo.description, newPodInfo.description), func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, enabled)()
|
||||||
|
var oldPodSpec *api.PodSpec
|
||||||
|
if oldPod != nil {
|
||||||
|
oldPodSpec = &oldPod.Spec
|
||||||
|
}
|
||||||
|
dropDisabledFields(&newPod.Spec, nil, oldPodSpec, nil)
|
||||||
|
// Old Pod should never be changed.
|
||||||
|
if diff := cmp.Diff(oldPod, oldPodInfo.pod()); diff != "" {
|
||||||
|
t.Errorf("old pod changed: %v", diff)
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case enabled || oldPodHasSchedulingGates:
|
||||||
|
// New Pod should not be changed if the feature is enabled, or if the old Pod had schedulingGates.
|
||||||
|
if diff := cmp.Diff(newPod, newPodInfo.pod()); diff != "" {
|
||||||
|
t.Errorf("new pod changed: %v", diff)
|
||||||
|
}
|
||||||
|
case newPodHasSchedulingGates:
|
||||||
|
// New Pod should be changed.
|
||||||
|
if reflect.DeepEqual(newPod, newPodInfo.pod()) {
|
||||||
|
t.Errorf("new pod was not changed")
|
||||||
|
}
|
||||||
|
// New Pod should not have SchedulingGates field.
|
||||||
|
if diff := cmp.Diff(newPod, podWithoutSchedulingGates()); diff != "" {
|
||||||
|
t.Errorf("new pod has SchedulingGates field: %v", diff)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// New pod should not need to be changed.
|
||||||
|
if diff := cmp.Diff(newPod, newPodInfo.pod()); diff != "" {
|
||||||
|
t.Errorf("new pod changed: %v", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -291,7 +291,7 @@ type PersistentVolume struct {
|
|||||||
// +optional
|
// +optional
|
||||||
metav1.ObjectMeta
|
metav1.ObjectMeta
|
||||||
|
|
||||||
//Spec defines a persistent volume owned by the cluster
|
// Spec defines a persistent volume owned by the cluster
|
||||||
// +optional
|
// +optional
|
||||||
Spec PersistentVolumeSpec
|
Spec PersistentVolumeSpec
|
||||||
|
|
||||||
@ -1977,10 +1977,10 @@ type EnvFromSource struct {
|
|||||||
// +optional
|
// +optional
|
||||||
Prefix string
|
Prefix string
|
||||||
// The ConfigMap to select from.
|
// The ConfigMap to select from.
|
||||||
//+optional
|
// +optional
|
||||||
ConfigMapRef *ConfigMapEnvSource
|
ConfigMapRef *ConfigMapEnvSource
|
||||||
// The Secret to select from.
|
// The Secret to select from.
|
||||||
//+optional
|
// +optional
|
||||||
SecretRef *SecretEnvSource
|
SecretRef *SecretEnvSource
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2428,6 +2428,9 @@ const (
|
|||||||
// PodReasonUnschedulable reason in PodScheduled PodCondition means that the scheduler
|
// PodReasonUnschedulable reason in PodScheduled PodCondition means that the scheduler
|
||||||
// can't schedule the pod right now, for example due to insufficient resources in the cluster.
|
// can't schedule the pod right now, for example due to insufficient resources in the cluster.
|
||||||
PodReasonUnschedulable = "Unschedulable"
|
PodReasonUnschedulable = "Unschedulable"
|
||||||
|
// PodReasonSchedulingGated reason in PodScheduled PodCondition means that the scheduler
|
||||||
|
// skips scheduling the pod because one or more scheduling gates are still present.
|
||||||
|
PodReasonSchedulingGated = "SchedulingGated"
|
||||||
// ContainersReady indicates whether all containers in the pod are ready.
|
// ContainersReady indicates whether all containers in the pod are ready.
|
||||||
ContainersReady PodConditionType = "ContainersReady"
|
ContainersReady PodConditionType = "ContainersReady"
|
||||||
// AlphaNoCompatGuaranteeDisruptionTarget indicates the pod is about to be deleted due to a
|
// AlphaNoCompatGuaranteeDisruptionTarget indicates the pod is about to be deleted due to a
|
||||||
@ -2502,7 +2505,7 @@ const (
|
|||||||
// over a set of nodes; that is, it represents the OR of the selectors represented
|
// over a set of nodes; that is, it represents the OR of the selectors represented
|
||||||
// by the node selector terms.
|
// by the node selector terms.
|
||||||
type NodeSelector struct {
|
type NodeSelector struct {
|
||||||
//Required. A list of node selector terms. The terms are ORed.
|
// Required. A list of node selector terms. The terms are ORed.
|
||||||
NodeSelectorTerms []NodeSelectorTerm
|
NodeSelectorTerms []NodeSelectorTerm
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2997,6 +3000,12 @@ type PodSpec struct {
|
|||||||
// - spec.containers[*].securityContext.runAsGroup
|
// - spec.containers[*].securityContext.runAsGroup
|
||||||
// +optional
|
// +optional
|
||||||
OS *PodOS
|
OS *PodOS
|
||||||
|
// SchedulingGates is an opaque list of values that if specified will block scheduling the pod.
|
||||||
|
// More info: https://git.k8s.io/enhancements/keps/sig-scheduling/3521-pod-scheduling-readiness.
|
||||||
|
//
|
||||||
|
// This is an alpha-level feature enabled by PodSchedulingReadiness feature gate.
|
||||||
|
// +optional
|
||||||
|
SchedulingGates []PodSchedulingGate
|
||||||
}
|
}
|
||||||
|
|
||||||
// OSName is the set of OS'es that can be used in OS.
|
// OSName is the set of OS'es that can be used in OS.
|
||||||
@ -3017,6 +3026,13 @@ type PodOS struct {
|
|||||||
Name OSName
|
Name OSName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PodSchedulingGate is associated to a Pod to guard its scheduling.
|
||||||
|
type PodSchedulingGate struct {
|
||||||
|
// Name of the scheduling gate.
|
||||||
|
// Each scheduling gate must have a unique name field.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
// HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the
|
// HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the
|
||||||
// pod's hosts file.
|
// pod's hosts file.
|
||||||
type HostAlias struct {
|
type HostAlias struct {
|
||||||
@ -3494,7 +3510,7 @@ type ReplicationControllerSpec struct {
|
|||||||
// insufficient replicas are detected. This reference is ignored if a Template is set.
|
// insufficient replicas are detected. This reference is ignored if a Template is set.
|
||||||
// Must be set before converting to a versioned API object
|
// Must be set before converting to a versioned API object
|
||||||
// +optional
|
// +optional
|
||||||
//TemplateRef *ObjectReference
|
// TemplateRef *ObjectReference
|
||||||
|
|
||||||
// Template is the object that describes the pod that will be created if
|
// Template is the object that describes the pod that will be created if
|
||||||
// insufficient replicas are detected. Internally, this takes precedence over a
|
// insufficient replicas are detected. Internally, this takes precedence over a
|
||||||
@ -4830,7 +4846,7 @@ type ObjectReference struct {
|
|||||||
|
|
||||||
// LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace.
|
// LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace.
|
||||||
type LocalObjectReference struct {
|
type LocalObjectReference struct {
|
||||||
//TODO: Add other useful fields. apiVersion, kind, uid?
|
// TODO: Add other useful fields. apiVersion, kind, uid?
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1013,7 +1013,7 @@ func validateGlusterfsPersistentVolumeSource(glusterfs *core.GlusterfsPersistent
|
|||||||
func validateFlockerVolumeSource(flocker *core.FlockerVolumeSource, fldPath *field.Path) field.ErrorList {
|
func validateFlockerVolumeSource(flocker *core.FlockerVolumeSource, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
if len(flocker.DatasetName) == 0 && len(flocker.DatasetUUID) == 0 {
|
if len(flocker.DatasetName) == 0 && len(flocker.DatasetUUID) == 0 {
|
||||||
//TODO: consider adding a RequiredOneOf() error for this and similar cases
|
// TODO: consider adding a RequiredOneOf() error for this and similar cases
|
||||||
allErrs = append(allErrs, field.Required(fldPath, "one of datasetName and datasetUUID is required"))
|
allErrs = append(allErrs, field.Required(fldPath, "one of datasetName and datasetUUID is required"))
|
||||||
}
|
}
|
||||||
if len(flocker.DatasetName) != 0 && len(flocker.DatasetUUID) != 0 {
|
if len(flocker.DatasetName) != 0 && len(flocker.DatasetUUID) != 0 {
|
||||||
@ -3272,6 +3272,22 @@ func validateReadinessGates(readinessGates []core.PodReadinessGate, fldPath *fie
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateSchedulingGates(schedulingGates []core.PodSchedulingGate, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
// There should be no duplicates in the list of scheduling gates.
|
||||||
|
seen := sets.String{}
|
||||||
|
for i, schedulingGate := range schedulingGates {
|
||||||
|
if schedulingGate.Name == "" {
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath.Index(i), "must not be empty"))
|
||||||
|
}
|
||||||
|
if seen.Has(schedulingGate.Name) {
|
||||||
|
allErrs = append(allErrs, field.Duplicate(fldPath.Index(i), schedulingGate.Name))
|
||||||
|
}
|
||||||
|
seen.Insert(schedulingGate.Name)
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
func validatePodDNSConfig(dnsConfig *core.PodDNSConfig, dnsPolicy *core.DNSPolicy, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
func validatePodDNSConfig(dnsConfig *core.PodDNSConfig, dnsPolicy *core.DNSPolicy, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
@ -3420,6 +3436,28 @@ func validateOnlyAddedTolerations(newTolerations []core.Toleration, oldToleratio
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateOnlyDeletedSchedulingGates(newGates, oldGates []core.PodSchedulingGate, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
if len(newGates) == 0 {
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
additionalGates := make(map[string]int)
|
||||||
|
for i, newGate := range newGates {
|
||||||
|
additionalGates[newGate.Name] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, oldGate := range oldGates {
|
||||||
|
delete(additionalGates, oldGate.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
for gate, i := range additionalGates {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(fldPath.Index(i).Child("name"), fmt.Sprintf("only deletion is allowed, but found new scheduling gate '%s'", gate)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
func ValidateHostAliases(hostAliases []core.HostAlias, fldPath *field.Path) field.ErrorList {
|
func ValidateHostAliases(hostAliases []core.HostAlias, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
for _, hostAlias := range hostAliases {
|
for _, hostAlias := range hostAliases {
|
||||||
@ -3577,7 +3615,7 @@ func validatePodIPs(pod *core.Pod) field.ErrorList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// There should be no duplicates in list of Pod.PodIPs
|
// There should be no duplicates in list of Pod.PodIPs
|
||||||
seen := sets.String{} //:= make(map[string]int)
|
seen := sets.String{} // := make(map[string]int)
|
||||||
for i, podIP := range pod.Status.PodIPs {
|
for i, podIP := range pod.Status.PodIPs {
|
||||||
if seen.Has(podIP.IP) {
|
if seen.Has(podIP.IP) {
|
||||||
allErrs = append(allErrs, field.Duplicate(podIPsField.Index(i), podIP))
|
allErrs = append(allErrs, field.Duplicate(podIPsField.Index(i), podIP))
|
||||||
@ -3611,6 +3649,7 @@ func ValidatePodSpec(spec *core.PodSpec, podMeta *metav1.ObjectMeta, fldPath *fi
|
|||||||
allErrs = append(allErrs, validateAffinity(spec.Affinity, fldPath.Child("affinity"))...)
|
allErrs = append(allErrs, validateAffinity(spec.Affinity, fldPath.Child("affinity"))...)
|
||||||
allErrs = append(allErrs, validatePodDNSConfig(spec.DNSConfig, &spec.DNSPolicy, fldPath.Child("dnsConfig"), opts)...)
|
allErrs = append(allErrs, validatePodDNSConfig(spec.DNSConfig, &spec.DNSPolicy, fldPath.Child("dnsConfig"), opts)...)
|
||||||
allErrs = append(allErrs, validateReadinessGates(spec.ReadinessGates, fldPath.Child("readinessGates"))...)
|
allErrs = append(allErrs, validateReadinessGates(spec.ReadinessGates, fldPath.Child("readinessGates"))...)
|
||||||
|
allErrs = append(allErrs, validateSchedulingGates(spec.SchedulingGates, fldPath.Child("schedulingGates"))...)
|
||||||
allErrs = append(allErrs, validateTopologySpreadConstraints(spec.TopologySpreadConstraints, fldPath.Child("topologySpreadConstraints"))...)
|
allErrs = append(allErrs, validateTopologySpreadConstraints(spec.TopologySpreadConstraints, fldPath.Child("topologySpreadConstraints"))...)
|
||||||
allErrs = append(allErrs, validateWindowsHostProcessPod(spec, fldPath)...)
|
allErrs = append(allErrs, validateWindowsHostProcessPod(spec, fldPath)...)
|
||||||
allErrs = append(allErrs, validateHostUsers(spec, fldPath)...)
|
allErrs = append(allErrs, validateHostUsers(spec, fldPath)...)
|
||||||
@ -4016,7 +4055,7 @@ func validatePodAntiAffinity(podAntiAffinity *core.PodAntiAffinity, fldPath *fie
|
|||||||
// if podAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil {
|
// if podAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil {
|
||||||
// allErrs = append(allErrs, validatePodAffinityTerms(podAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution, false,
|
// allErrs = append(allErrs, validatePodAffinityTerms(podAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution, false,
|
||||||
// fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...)
|
// fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...)
|
||||||
//}
|
// }
|
||||||
if podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
|
if podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
|
||||||
allErrs = append(allErrs, validatePodAffinityTerms(podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution,
|
allErrs = append(allErrs, validatePodAffinityTerms(podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution,
|
||||||
fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...)
|
fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...)
|
||||||
@ -4051,7 +4090,7 @@ func validatePodAffinity(podAffinity *core.PodAffinity, fldPath *field.Path) fie
|
|||||||
// if podAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil {
|
// if podAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil {
|
||||||
// allErrs = append(allErrs, validatePodAffinityTerms(podAffinity.RequiredDuringSchedulingRequiredDuringExecution, false,
|
// allErrs = append(allErrs, validatePodAffinityTerms(podAffinity.RequiredDuringSchedulingRequiredDuringExecution, false,
|
||||||
// fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...)
|
// fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...)
|
||||||
//}
|
// }
|
||||||
if podAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
|
if podAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
|
||||||
allErrs = append(allErrs, validatePodAffinityTerms(podAffinity.RequiredDuringSchedulingIgnoredDuringExecution,
|
allErrs = append(allErrs, validatePodAffinityTerms(podAffinity.RequiredDuringSchedulingIgnoredDuringExecution,
|
||||||
fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...)
|
fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...)
|
||||||
@ -4258,7 +4297,7 @@ func ValidatePodSecurityContext(securityContext *core.PodSecurityContext, spec *
|
|||||||
func ValidateContainerUpdates(newContainers, oldContainers []core.Container, fldPath *field.Path) (allErrs field.ErrorList, stop bool) {
|
func ValidateContainerUpdates(newContainers, oldContainers []core.Container, fldPath *field.Path) (allErrs field.ErrorList, stop bool) {
|
||||||
allErrs = field.ErrorList{}
|
allErrs = field.ErrorList{}
|
||||||
if len(newContainers) != len(oldContainers) {
|
if len(newContainers) != len(oldContainers) {
|
||||||
//TODO: Pinpoint the specific container that causes the invalid error after we have strategic merge diff
|
// TODO: Pinpoint the specific container that causes the invalid error after we have strategic merge diff
|
||||||
allErrs = append(allErrs, field.Forbidden(fldPath, "pod updates may not add or remove containers"))
|
allErrs = append(allErrs, field.Forbidden(fldPath, "pod updates may not add or remove containers"))
|
||||||
return allErrs, true
|
return allErrs, true
|
||||||
}
|
}
|
||||||
@ -4285,6 +4324,11 @@ func ValidatePodCreate(pod *core.Pod, opts PodValidationOptions) field.ErrorList
|
|||||||
if len(pod.Spec.EphemeralContainers) > 0 {
|
if len(pod.Spec.EphemeralContainers) > 0 {
|
||||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("ephemeralContainers"), "cannot be set on create"))
|
allErrs = append(allErrs, field.Forbidden(fldPath.Child("ephemeralContainers"), "cannot be set on create"))
|
||||||
}
|
}
|
||||||
|
// A Pod cannot be assigned a Node if there are remaining scheduling gates.
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness) &&
|
||||||
|
pod.Spec.NodeName != "" && len(pod.Spec.SchedulingGates) != 0 {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared"))
|
||||||
|
}
|
||||||
allErrs = append(allErrs, validateSeccompAnnotationsAndFields(pod.ObjectMeta, &pod.Spec, fldPath)...)
|
allErrs = append(allErrs, validateSeccompAnnotationsAndFields(pod.ObjectMeta, &pod.Spec, fldPath)...)
|
||||||
|
|
||||||
return allErrs
|
return allErrs
|
||||||
@ -4370,6 +4414,7 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
|
|||||||
// 2. spec.initContainers[*].image
|
// 2. spec.initContainers[*].image
|
||||||
// 3. spec.activeDeadlineSeconds
|
// 3. spec.activeDeadlineSeconds
|
||||||
// 4. spec.terminationGracePeriodSeconds
|
// 4. spec.terminationGracePeriodSeconds
|
||||||
|
// 5. spec.schedulingGates
|
||||||
|
|
||||||
containerErrs, stop := ValidateContainerUpdates(newPod.Spec.Containers, oldPod.Spec.Containers, specPath.Child("containers"))
|
containerErrs, stop := ValidateContainerUpdates(newPod.Spec.Containers, oldPod.Spec.Containers, specPath.Child("containers"))
|
||||||
allErrs = append(allErrs, containerErrs...)
|
allErrs = append(allErrs, containerErrs...)
|
||||||
@ -4405,6 +4450,9 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
|
|||||||
// Allow only additions to tolerations updates.
|
// Allow only additions to tolerations updates.
|
||||||
allErrs = append(allErrs, validateOnlyAddedTolerations(newPod.Spec.Tolerations, oldPod.Spec.Tolerations, specPath.Child("tolerations"))...)
|
allErrs = append(allErrs, validateOnlyAddedTolerations(newPod.Spec.Tolerations, oldPod.Spec.Tolerations, specPath.Child("tolerations"))...)
|
||||||
|
|
||||||
|
// Allow only deletions to schedulingGates updates.
|
||||||
|
allErrs = append(allErrs, validateOnlyDeletedSchedulingGates(newPod.Spec.SchedulingGates, oldPod.Spec.SchedulingGates, specPath.Child("schedulingGates"))...)
|
||||||
|
|
||||||
// the last thing to check is pod spec equality. If the pod specs are equal, then we can simply return the errors we have
|
// the last thing to check is pod spec equality. If the pod specs are equal, then we can simply return the errors we have
|
||||||
// so far and save the cost of a deep copy.
|
// so far and save the cost of a deep copy.
|
||||||
if apiequality.Semantic.DeepEqual(newPod.Spec, oldPod.Spec) {
|
if apiequality.Semantic.DeepEqual(newPod.Spec, oldPod.Spec) {
|
||||||
@ -4433,6 +4481,8 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
|
|||||||
activeDeadlineSeconds := *oldPod.Spec.ActiveDeadlineSeconds
|
activeDeadlineSeconds := *oldPod.Spec.ActiveDeadlineSeconds
|
||||||
mungedPodSpec.ActiveDeadlineSeconds = &activeDeadlineSeconds
|
mungedPodSpec.ActiveDeadlineSeconds = &activeDeadlineSeconds
|
||||||
}
|
}
|
||||||
|
// munge spec.schedulingGates
|
||||||
|
mungedPodSpec.SchedulingGates = oldPod.Spec.SchedulingGates // +k8s:verify-mutation:reason=clone
|
||||||
// tolerations are checked before the deep copy, so munge those too
|
// tolerations are checked before the deep copy, so munge those too
|
||||||
mungedPodSpec.Tolerations = oldPod.Spec.Tolerations // +k8s:verify-mutation:reason=clone
|
mungedPodSpec.Tolerations = oldPod.Spec.Tolerations // +k8s:verify-mutation:reason=clone
|
||||||
|
|
||||||
@ -4444,7 +4494,7 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
|
|||||||
|
|
||||||
if !apiequality.Semantic.DeepEqual(mungedPodSpec, oldPod.Spec) {
|
if !apiequality.Semantic.DeepEqual(mungedPodSpec, oldPod.Spec) {
|
||||||
// This diff isn't perfect, but it's a helluva lot better an "I'm not going to tell you what the difference is".
|
// This diff isn't perfect, but it's a helluva lot better an "I'm not going to tell you what the difference is".
|
||||||
//TODO: Pinpoint the specific field that causes the invalid error after we have strategic merge diff
|
// TODO: Pinpoint the specific field that causes the invalid error after we have strategic merge diff
|
||||||
specDiff := cmp.Diff(oldPod.Spec, mungedPodSpec)
|
specDiff := cmp.Diff(oldPod.Spec, mungedPodSpec)
|
||||||
allErrs = append(allErrs, field.Forbidden(specPath, fmt.Sprintf("pod updates may not change fields other than `spec.containers[*].image`, `spec.initContainers[*].image`, `spec.activeDeadlineSeconds`, `spec.tolerations` (only additions to existing tolerations) or `spec.terminationGracePeriodSeconds` (allow it to be set to 1 if it was previously negative)\n%v", specDiff)))
|
allErrs = append(allErrs, field.Forbidden(specPath, fmt.Sprintf("pod updates may not change fields other than `spec.containers[*].image`, `spec.initContainers[*].image`, `spec.activeDeadlineSeconds`, `spec.tolerations` (only additions to existing tolerations) or `spec.terminationGracePeriodSeconds` (allow it to be set to 1 if it was previously negative)\n%v", specDiff)))
|
||||||
}
|
}
|
||||||
@ -6121,7 +6171,7 @@ func validateEndpointSubsets(subsets []core.EndpointSubset, fldPath *field.Path)
|
|||||||
|
|
||||||
// EndpointSubsets must include endpoint address. For headless service, we allow its endpoints not to have ports.
|
// EndpointSubsets must include endpoint address. For headless service, we allow its endpoints not to have ports.
|
||||||
if len(ss.Addresses) == 0 && len(ss.NotReadyAddresses) == 0 {
|
if len(ss.Addresses) == 0 && len(ss.NotReadyAddresses) == 0 {
|
||||||
//TODO: consider adding a RequiredOneOf() error for this and similar cases
|
// TODO: consider adding a RequiredOneOf() error for this and similar cases
|
||||||
allErrs = append(allErrs, field.Required(idxPath, "must specify `addresses` or `notReadyAddresses`"))
|
allErrs = append(allErrs, field.Required(idxPath, "must specify `addresses` or `notReadyAddresses`"))
|
||||||
}
|
}
|
||||||
for addr := range ss.Addresses {
|
for addr := range ss.Addresses {
|
||||||
@ -6210,7 +6260,7 @@ func validateEndpointPort(port *core.EndpointPort, requireName bool, fldPath *fi
|
|||||||
// ValidateSecurityContext ensures the security context contains valid settings
|
// ValidateSecurityContext ensures the security context contains valid settings
|
||||||
func ValidateSecurityContext(sc *core.SecurityContext, fldPath *field.Path) field.ErrorList {
|
func ValidateSecurityContext(sc *core.SecurityContext, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
//this should only be true for testing since SecurityContext is defaulted by the core
|
// this should only be true for testing since SecurityContext is defaulted by the core
|
||||||
if sc == nil {
|
if sc == nil {
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
@ -6726,7 +6776,7 @@ func ValidateServiceClusterIPsRelatedFields(service *core.Service) field.ErrorLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IPFamilyPolicy stand alone validation
|
// IPFamilyPolicy stand alone validation
|
||||||
//note: nil is ok, defaulted in alloc check registry/core/service/*
|
// note: nil is ok, defaulted in alloc check registry/core/service/*
|
||||||
if service.Spec.IPFamilyPolicy != nil {
|
if service.Spec.IPFamilyPolicy != nil {
|
||||||
// must have a supported value
|
// must have a supported value
|
||||||
if !supportedServiceIPFamilyPolicy.Has(string(*(service.Spec.IPFamilyPolicy))) {
|
if !supportedServiceIPFamilyPolicy.Has(string(*(service.Spec.IPFamilyPolicy))) {
|
||||||
|
@ -703,7 +703,7 @@ func TestValidatePersistentVolumeSourceUpdate(t *testing.T) {
|
|||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
}
|
}
|
||||||
|
|
||||||
//longSecretRef refers to the secretRefs which are validated with IsDNS1123Subdomain
|
// longSecretRef refers to the secretRefs which are validated with IsDNS1123Subdomain
|
||||||
longSecretName := "key-name.example.com"
|
longSecretName := "key-name.example.com"
|
||||||
longSecretRef := &core.SecretReference{
|
longSecretRef := &core.SecretReference{
|
||||||
Name: longSecretName,
|
Name: longSecretName,
|
||||||
@ -10794,6 +10794,91 @@ func TestValidatePod(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidatePodCreateWithSchedulingGates(t *testing.T) {
|
||||||
|
applyEssentials := func(pod *core.Pod) {
|
||||||
|
pod.Spec.Containers = []core.Container{
|
||||||
|
{Name: "con", Image: "pause", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
|
||||||
|
}
|
||||||
|
pod.Spec.RestartPolicy = core.RestartPolicyAlways
|
||||||
|
pod.Spec.DNSPolicy = core.DNSClusterFirst
|
||||||
|
}
|
||||||
|
fldPath := field.NewPath("spec")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pod *core.Pod
|
||||||
|
featureEnabled bool
|
||||||
|
wantFieldErrors field.ErrorList
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "create a Pod with nodeName and schedulingGates, feature disabled",
|
||||||
|
pod: &core.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
NodeName: "node",
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
featureEnabled: false,
|
||||||
|
wantFieldErrors: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create a Pod with nodeName and schedulingGates, feature enabled",
|
||||||
|
pod: &core.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
NodeName: "node",
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
featureEnabled: true,
|
||||||
|
wantFieldErrors: []*field.Error{field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create a Pod with schedulingGates, feature disabled",
|
||||||
|
pod: &core.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
featureEnabled: false,
|
||||||
|
wantFieldErrors: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create a Pod with schedulingGates, feature enabled",
|
||||||
|
pod: &core.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
featureEnabled: true,
|
||||||
|
wantFieldErrors: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, tt.featureEnabled)()
|
||||||
|
|
||||||
|
applyEssentials(tt.pod)
|
||||||
|
errs := ValidatePodCreate(tt.pod, PodValidationOptions{})
|
||||||
|
if diff := cmp.Diff(tt.wantFieldErrors, errs); diff != "" {
|
||||||
|
t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestValidatePodUpdate(t *testing.T) {
|
func TestValidatePodUpdate(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
activeDeadlineSecondsZero = int64(0)
|
activeDeadlineSecondsZero = int64(0)
|
||||||
@ -11698,6 +11783,54 @@ func TestValidatePodUpdate(t *testing.T) {
|
|||||||
err: "Forbidden: pod updates may not change fields other than ",
|
err: "Forbidden: pod updates may not change fields other than ",
|
||||||
test: "update pod spec OS to a valid value, featuregate disabled",
|
test: "update pod spec OS to a valid value, featuregate disabled",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
new: core.Pod{
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
old: core.Pod{},
|
||||||
|
err: "Forbidden: only deletion is allowed, but found new scheduling gate 'foo'",
|
||||||
|
test: "update pod spec schedulingGates: add new scheduling gate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: core.Pod{
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{{Name: "bar"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
old: core.Pod{
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: "Forbidden: only deletion is allowed, but found new scheduling gate 'bar'",
|
||||||
|
test: "update pod spec schedulingGates: mutating an existing scheduling gate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: core.Pod{
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
old: core.Pod{
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}, {Name: "bar"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: "Forbidden: only deletion is allowed, but found new scheduling gate 'baz'",
|
||||||
|
test: "update pod spec schedulingGates: mutating an existing scheduling gate along with deletion",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: core.Pod{},
|
||||||
|
old: core.Pod{
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: "",
|
||||||
|
test: "update pod spec schedulingGates: legal deletion",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
test.new.ObjectMeta.ResourceVersion = "1"
|
test.new.ObjectMeta.ResourceVersion = "1"
|
||||||
@ -18484,6 +18617,7 @@ func TestValidateOSFields(t *testing.T) {
|
|||||||
"RestartPolicy",
|
"RestartPolicy",
|
||||||
"RuntimeClassName",
|
"RuntimeClassName",
|
||||||
"SchedulerName",
|
"SchedulerName",
|
||||||
|
"SchedulingGates[*].Name",
|
||||||
"SecurityContext.RunAsNonRoot",
|
"SecurityContext.RunAsNonRoot",
|
||||||
"ServiceAccountName",
|
"ServiceAccountName",
|
||||||
"SetHostnameAsFQDN",
|
"SetHostnameAsFQDN",
|
||||||
@ -18520,6 +18654,71 @@ func TestValidateOSFields(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateSchedulingGates(t *testing.T) {
|
||||||
|
fieldPath := field.NewPath("field")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
schedulingGates []core.PodSchedulingGate
|
||||||
|
wantFieldErrors field.ErrorList
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil gates",
|
||||||
|
schedulingGates: nil,
|
||||||
|
wantFieldErrors: field.ErrorList{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty string in gates",
|
||||||
|
schedulingGates: []core.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
{Name: ""},
|
||||||
|
},
|
||||||
|
wantFieldErrors: []*field.Error{field.Required(fieldPath.Index(1), "must not be empty")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "legal gates",
|
||||||
|
schedulingGates: []core.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
{Name: "bar"},
|
||||||
|
},
|
||||||
|
wantFieldErrors: field.ErrorList{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duplicated gates (single duplication)",
|
||||||
|
schedulingGates: []core.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
{Name: "bar"},
|
||||||
|
{Name: "bar"},
|
||||||
|
},
|
||||||
|
wantFieldErrors: []*field.Error{field.Duplicate(fieldPath.Index(2), "bar")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duplicated gates (multiple duplications)",
|
||||||
|
schedulingGates: []core.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
{Name: "bar"},
|
||||||
|
{Name: "foo"},
|
||||||
|
{Name: "baz"},
|
||||||
|
{Name: "foo"},
|
||||||
|
{Name: "bar"},
|
||||||
|
},
|
||||||
|
wantFieldErrors: field.ErrorList{
|
||||||
|
field.Duplicate(fieldPath.Index(2), "foo"),
|
||||||
|
field.Duplicate(fieldPath.Index(4), "foo"),
|
||||||
|
field.Duplicate(fieldPath.Index(5), "bar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
errs := validateSchedulingGates(tt.schedulingGates, fieldPath)
|
||||||
|
if diff := cmp.Diff(tt.wantFieldErrors, errs); diff != "" {
|
||||||
|
t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// collectResourcePaths traverses the object, computing all the struct paths.
|
// collectResourcePaths traverses the object, computing all the struct paths.
|
||||||
func collectResourcePaths(t *testing.T, skipRecurseList sets.String, tp reflect.Type, path *field.Path) sets.String {
|
func collectResourcePaths(t *testing.T, skipRecurseList sets.String, tp reflect.Type, path *field.Path) sets.String {
|
||||||
if pathStr := path.String(); len(pathStr) > 0 && skipRecurseList.Has(pathStr) {
|
if pathStr := path.String(); len(pathStr) > 0 && skipRecurseList.Has(pathStr) {
|
||||||
@ -18664,7 +18863,7 @@ func TestValidateSecurityContext(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//setup data
|
// setup data
|
||||||
allSettings := fullValidSC()
|
allSettings := fullValidSC()
|
||||||
noCaps := fullValidSC()
|
noCaps := fullValidSC()
|
||||||
noCaps.Capabilities = nil
|
noCaps.Capabilities = nil
|
||||||
|
@ -643,6 +643,13 @@ const (
|
|||||||
// sandbox creation and network configuration completes successfully
|
// sandbox creation and network configuration completes successfully
|
||||||
PodHasNetworkCondition featuregate.Feature = "PodHasNetworkCondition"
|
PodHasNetworkCondition featuregate.Feature = "PodHasNetworkCondition"
|
||||||
|
|
||||||
|
// owner: @Huang-Wei
|
||||||
|
// kep: https://kep.k8s.io/3521
|
||||||
|
// alpha: v1.26
|
||||||
|
//
|
||||||
|
// Enable users to specify when a Pod is ready for scheduling.
|
||||||
|
PodSchedulingReadiness featuregate.Feature = "PodSchedulingReadiness"
|
||||||
|
|
||||||
// owner: @liggitt, @tallclair, sig-auth
|
// owner: @liggitt, @tallclair, sig-auth
|
||||||
// alpha: v1.22
|
// alpha: v1.22
|
||||||
// beta: v1.23
|
// beta: v1.23
|
||||||
@ -995,6 +1002,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
|||||||
|
|
||||||
PodHasNetworkCondition: {Default: false, PreRelease: featuregate.Alpha},
|
PodHasNetworkCondition: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
|
PodSchedulingReadiness: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
PodSecurity: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
PodSecurity: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
|
|
||||||
ProbeTerminationGracePeriod: {Default: true, PreRelease: featuregate.Beta}, // Default to true in beta 1.25
|
ProbeTerminationGracePeriod: {Default: true, PreRelease: featuregate.Beta}, // Default to true in beta 1.25
|
||||||
|
@ -767,6 +767,13 @@ func printPod(pod *api.Pod, options printers.GenerateOptions) ([]metav1.TableRow
|
|||||||
reason = pod.Status.Reason
|
reason = pod.Status.Reason
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the Pod carries {type:PodScheduled, reason:WaitingForGates}, set reason to 'SchedulingGated'.
|
||||||
|
for _, condition := range pod.Status.Conditions {
|
||||||
|
if condition.Type == api.PodScheduled && condition.Reason == api.PodReasonSchedulingGated {
|
||||||
|
reason = api.PodReasonSchedulingGated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
row := metav1.TableRow{
|
row := metav1.TableRow{
|
||||||
Object: runtime.RawExtension{Object: pod},
|
Object: runtime.RawExtension{Object: pod},
|
||||||
}
|
}
|
||||||
|
@ -1502,6 +1502,24 @@ func TestPrintPod(t *testing.T) {
|
|||||||
},
|
},
|
||||||
[]metav1.TableRow{{Cells: []interface{}{"test14", "2/2", "Running", "9 (5d ago)", "<unknown>"}}},
|
[]metav1.TableRow{{Cells: []interface{}{"test14", "2/2", "Running", "9 (5d ago)", "<unknown>"}}},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Test PodScheduled condition with reason WaitingForGates
|
||||||
|
api.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "test15"},
|
||||||
|
Spec: api.PodSpec{Containers: make([]api.Container, 2)},
|
||||||
|
Status: api.PodStatus{
|
||||||
|
Phase: "podPhase",
|
||||||
|
Conditions: []api.PodCondition{
|
||||||
|
{
|
||||||
|
Type: api.PodScheduled,
|
||||||
|
Status: api.ConditionFalse,
|
||||||
|
Reason: api.PodReasonSchedulingGated,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]metav1.TableRow{{Cells: []interface{}{"test15", "0/2", api.PodReasonSchedulingGated, "0", "<unknown>"}}},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
|
@ -33,10 +33,12 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/storage"
|
"k8s.io/apiserver/pkg/storage"
|
||||||
storeerr "k8s.io/apiserver/pkg/storage/errors"
|
storeerr "k8s.io/apiserver/pkg/storage/errors"
|
||||||
"k8s.io/apiserver/pkg/util/dryrun"
|
"k8s.io/apiserver/pkg/util/dryrun"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
policyclient "k8s.io/client-go/kubernetes/typed/policy/v1"
|
policyclient "k8s.io/client-go/kubernetes/typed/policy/v1"
|
||||||
podutil "k8s.io/kubernetes/pkg/api/pod"
|
podutil "k8s.io/kubernetes/pkg/api/pod"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/client"
|
"k8s.io/kubernetes/pkg/kubelet/client"
|
||||||
"k8s.io/kubernetes/pkg/printers"
|
"k8s.io/kubernetes/pkg/printers"
|
||||||
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
||||||
@ -221,6 +223,10 @@ func (r *BindingREST) setPodHostAndAnnotations(ctx context.Context, podUID types
|
|||||||
if pod.Spec.NodeName != "" {
|
if pod.Spec.NodeName != "" {
|
||||||
return nil, fmt.Errorf("pod %v is already assigned to node %q", pod.Name, pod.Spec.NodeName)
|
return nil, fmt.Errorf("pod %v is already assigned to node %q", pod.Name, pod.Spec.NodeName)
|
||||||
}
|
}
|
||||||
|
// Reject binding to a scheduling un-ready Pod.
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness) && len(pod.Spec.SchedulingGates) != 0 {
|
||||||
|
return nil, fmt.Errorf("pod %v has non-empty .spec.schedulingGates", pod.Name)
|
||||||
|
}
|
||||||
pod.Spec.NodeName = machine
|
pod.Spec.NodeName = machine
|
||||||
if pod.Annotations == nil {
|
if pod.Annotations == nil {
|
||||||
pod.Annotations = make(map[string]string)
|
pod.Annotations = make(map[string]string)
|
||||||
|
@ -18,6 +18,7 @@ package storage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
goerrors "errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
@ -41,7 +42,10 @@ import (
|
|||||||
apiserverstorage "k8s.io/apiserver/pkg/storage"
|
apiserverstorage "k8s.io/apiserver/pkg/storage"
|
||||||
storeerr "k8s.io/apiserver/pkg/storage/errors"
|
storeerr "k8s.io/apiserver/pkg/storage/errors"
|
||||||
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
|
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/registry/registrytest"
|
"k8s.io/kubernetes/pkg/registry/registrytest"
|
||||||
"k8s.io/kubernetes/pkg/securitycontext"
|
"k8s.io/kubernetes/pkg/securitycontext"
|
||||||
)
|
)
|
||||||
@ -745,6 +749,77 @@ func TestEtcdCreateWithConflict(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEtcdCreateWithSchedulingGates(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
featureEnabled bool
|
||||||
|
schedulingGates []api.PodSchedulingGate
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "pod with non-nil schedulingGates, feature disabled",
|
||||||
|
featureEnabled: false,
|
||||||
|
schedulingGates: []api.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
{Name: "bar"},
|
||||||
|
},
|
||||||
|
wantErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pod with non-nil schedulingGates, feature enabled",
|
||||||
|
featureEnabled: true,
|
||||||
|
schedulingGates: []api.PodSchedulingGate{
|
||||||
|
{Name: "foo"},
|
||||||
|
{Name: "bar"},
|
||||||
|
},
|
||||||
|
wantErr: goerrors.New(`Operation cannot be fulfilled on pods/binding "foo": pod foo has non-empty .spec.schedulingGates`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pod with nil schedulingGates, feature disabled",
|
||||||
|
featureEnabled: false,
|
||||||
|
schedulingGates: nil,
|
||||||
|
wantErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pod with nil schedulingGates, feature enabled",
|
||||||
|
featureEnabled: true,
|
||||||
|
schedulingGates: nil,
|
||||||
|
wantErr: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, tt.featureEnabled)()
|
||||||
|
storage, bindingStorage, _, server := newStorage(t)
|
||||||
|
defer server.Terminate(t)
|
||||||
|
defer storage.Store.DestroyFunc()
|
||||||
|
ctx := genericapirequest.NewDefaultContext()
|
||||||
|
|
||||||
|
pod := validNewPod()
|
||||||
|
pod.Spec.SchedulingGates = tt.schedulingGates
|
||||||
|
if _, err := storage.Create(ctx, pod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
_, err := bindingStorage.Create(ctx, "foo", &api.Binding{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "foo"},
|
||||||
|
Target: api.ObjectReference{Name: "machine"},
|
||||||
|
}, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
|
||||||
|
if tt.wantErr == nil {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Want nil err, but got %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Want %v, but got nil err", tt.wantErr)
|
||||||
|
} else if tt.wantErr.Error() != err.Error() {
|
||||||
|
t.Errorf("Want %v, but got %v", tt.wantErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func validNewBinding() *api.Binding {
|
func validNewBinding() *api.Binding {
|
||||||
return &api.Binding{
|
return &api.Binding{
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||||
|
@ -38,12 +38,14 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/registry/generic"
|
"k8s.io/apiserver/pkg/registry/generic"
|
||||||
"k8s.io/apiserver/pkg/storage"
|
"k8s.io/apiserver/pkg/storage"
|
||||||
"k8s.io/apiserver/pkg/storage/names"
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
podutil "k8s.io/kubernetes/pkg/api/pod"
|
podutil "k8s.io/kubernetes/pkg/api/pod"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/helper/qos"
|
"k8s.io/kubernetes/pkg/apis/core/helper/qos"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/client"
|
"k8s.io/kubernetes/pkg/kubelet/client"
|
||||||
proxyutil "k8s.io/kubernetes/pkg/proxy/util"
|
proxyutil "k8s.io/kubernetes/pkg/proxy/util"
|
||||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
@ -87,6 +89,7 @@ func (podStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
|||||||
podutil.DropDisabledPodFields(pod, nil)
|
podutil.DropDisabledPodFields(pod, nil)
|
||||||
|
|
||||||
applySeccompVersionSkew(pod)
|
applySeccompVersionSkew(pod)
|
||||||
|
applyWaitingForSchedulingGatesCondition(pod)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
||||||
@ -642,6 +645,29 @@ func validateContainer(container string, pod *api.Pod) (string, error) {
|
|||||||
return container, nil
|
return container, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// applyWaitingForSchedulingGatesCondition adds a {type:PodScheduled, reason:WaitingForGates} condition
|
||||||
|
// to a new-created Pod if necessary.
|
||||||
|
func applyWaitingForSchedulingGatesCondition(pod *api.Pod) {
|
||||||
|
if !utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness) ||
|
||||||
|
len(pod.Spec.SchedulingGates) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If found a condition with type PodScheduled, return.
|
||||||
|
for _, condition := range pod.Status.Conditions {
|
||||||
|
if condition.Type == api.PodScheduled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pod.Status.Conditions = append(pod.Status.Conditions, api.PodCondition{
|
||||||
|
Type: api.PodScheduled,
|
||||||
|
Status: api.ConditionFalse,
|
||||||
|
Reason: api.PodReasonSchedulingGated,
|
||||||
|
Message: "Scheduling is blocked due to non-empty scheduling gates",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// applySeccompVersionSkew implements the version skew behavior described in:
|
// applySeccompVersionSkew implements the version skew behavior described in:
|
||||||
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/135-seccomp#version-skew-strategy
|
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/135-seccomp#version-skew-strategy
|
||||||
// Note that we dropped copying the field to annotation synchronization in
|
// Note that we dropped copying the field to annotation synchronization in
|
||||||
|
@ -35,9 +35,12 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
apitesting "k8s.io/kubernetes/pkg/api/testing"
|
apitesting "k8s.io/kubernetes/pkg/api/testing"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/client"
|
"k8s.io/kubernetes/pkg/kubelet/client"
|
||||||
utilpointer "k8s.io/utils/pointer"
|
utilpointer "k8s.io/utils/pointer"
|
||||||
|
|
||||||
@ -261,6 +264,72 @@ func TestGetPodQOS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWaitingForGatesCondition(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pod *api.Pod
|
||||||
|
featureEnabled bool
|
||||||
|
want api.PodCondition
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "pod without .spec.schedulingGates, feature disabled",
|
||||||
|
pod: &api.Pod{},
|
||||||
|
featureEnabled: false,
|
||||||
|
want: api.PodCondition{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pod without .spec.schedulingGates, feature enabled",
|
||||||
|
pod: &api.Pod{},
|
||||||
|
featureEnabled: true,
|
||||||
|
want: api.PodCondition{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pod with .spec.schedulingGates, feature disabled",
|
||||||
|
pod: &api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
SchedulingGates: []api.PodSchedulingGate{{Name: "foo"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
featureEnabled: false,
|
||||||
|
want: api.PodCondition{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pod with .spec.schedulingGates, feature enabled",
|
||||||
|
pod: &api.Pod{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
SchedulingGates: []api.PodSchedulingGate{{Name: "foo"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
featureEnabled: true,
|
||||||
|
want: api.PodCondition{
|
||||||
|
Type: api.PodScheduled,
|
||||||
|
Status: api.ConditionFalse,
|
||||||
|
Reason: api.PodReasonSchedulingGated,
|
||||||
|
Message: "Scheduling is blocked due to non-empty scheduling gates",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, tt.featureEnabled)()
|
||||||
|
|
||||||
|
Strategy.PrepareForCreate(genericapirequest.NewContext(), tt.pod)
|
||||||
|
var got api.PodCondition
|
||||||
|
for _, condition := range tt.pod.Status.Conditions {
|
||||||
|
if condition.Type == api.PodScheduled {
|
||||||
|
got = condition
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := cmp.Diff(tt.want, got); diff != "" {
|
||||||
|
t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCheckGracefulDelete(t *testing.T) {
|
func TestCheckGracefulDelete(t *testing.T) {
|
||||||
defaultGracePeriod := int64(30)
|
defaultGracePeriod := int64(30)
|
||||||
tcs := []struct {
|
tcs := []struct {
|
||||||
|
@ -1663,7 +1663,7 @@ type ServiceAccountTokenProjection struct {
|
|||||||
// must identify itself with an identifier specified in the audience of the
|
// must identify itself with an identifier specified in the audience of the
|
||||||
// token, and otherwise should reject the token. The audience defaults to the
|
// token, and otherwise should reject the token. The audience defaults to the
|
||||||
// identifier of the apiserver.
|
// identifier of the apiserver.
|
||||||
//+optional
|
// +optional
|
||||||
Audience string `json:"audience,omitempty" protobuf:"bytes,1,rep,name=audience"`
|
Audience string `json:"audience,omitempty" protobuf:"bytes,1,rep,name=audience"`
|
||||||
// expirationSeconds is the requested duration of validity of the service
|
// expirationSeconds is the requested duration of validity of the service
|
||||||
// account token. As the token approaches expiration, the kubelet volume
|
// account token. As the token approaches expiration, the kubelet volume
|
||||||
@ -1671,7 +1671,7 @@ type ServiceAccountTokenProjection struct {
|
|||||||
// start trying to rotate the token if the token is older than 80 percent of
|
// start trying to rotate the token if the token is older than 80 percent of
|
||||||
// its time to live or if the token is older than 24 hours.Defaults to 1 hour
|
// its time to live or if the token is older than 24 hours.Defaults to 1 hour
|
||||||
// and must be at least 10 minutes.
|
// and must be at least 10 minutes.
|
||||||
//+optional
|
// +optional
|
||||||
ExpirationSeconds *int64 `json:"expirationSeconds,omitempty" protobuf:"varint,2,opt,name=expirationSeconds"`
|
ExpirationSeconds *int64 `json:"expirationSeconds,omitempty" protobuf:"varint,2,opt,name=expirationSeconds"`
|
||||||
// path is the path relative to the mount point of the file to project the
|
// path is the path relative to the mount point of the file to project the
|
||||||
// token into.
|
// token into.
|
||||||
@ -2666,6 +2666,10 @@ const (
|
|||||||
// can't schedule the pod right now, for example due to insufficient resources in the cluster.
|
// can't schedule the pod right now, for example due to insufficient resources in the cluster.
|
||||||
PodReasonUnschedulable = "Unschedulable"
|
PodReasonUnschedulable = "Unschedulable"
|
||||||
|
|
||||||
|
// PodReasonSchedulingGated reason in PodScheduled PodCondition means that the scheduler
|
||||||
|
// skips scheduling the pod because one or more scheduling gates are still present.
|
||||||
|
PodReasonSchedulingGated = "SchedulingGated"
|
||||||
|
|
||||||
// PodReasonSchedulerError reason in PodScheduled PodCondition means that some internal error happens
|
// PodReasonSchedulerError reason in PodScheduled PodCondition means that some internal error happens
|
||||||
// during scheduling, for example due to nodeAffinity parsing errors.
|
// during scheduling, for example due to nodeAffinity parsing errors.
|
||||||
PodReasonSchedulerError = "SchedulerError"
|
PodReasonSchedulerError = "SchedulerError"
|
||||||
@ -2743,7 +2747,7 @@ const (
|
|||||||
// by the node selector terms.
|
// by the node selector terms.
|
||||||
// +structType=atomic
|
// +structType=atomic
|
||||||
type NodeSelector struct {
|
type NodeSelector struct {
|
||||||
//Required. A list of node selector terms. The terms are ORed.
|
// Required. A list of node selector terms. The terms are ORed.
|
||||||
NodeSelectorTerms []NodeSelectorTerm `json:"nodeSelectorTerms" protobuf:"bytes,1,rep,name=nodeSelectorTerms"`
|
NodeSelectorTerms []NodeSelectorTerm `json:"nodeSelectorTerms" protobuf:"bytes,1,rep,name=nodeSelectorTerms"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3327,6 +3331,16 @@ type PodSpec struct {
|
|||||||
// +k8s:conversion-gen=false
|
// +k8s:conversion-gen=false
|
||||||
// +optional
|
// +optional
|
||||||
HostUsers *bool `json:"hostUsers,omitempty" protobuf:"bytes,37,opt,name=hostUsers"`
|
HostUsers *bool `json:"hostUsers,omitempty" protobuf:"bytes,37,opt,name=hostUsers"`
|
||||||
|
// SchedulingGates is an opaque list of values that if specified will block scheduling the pod.
|
||||||
|
// More info: https://git.k8s.io/enhancements/keps/sig-scheduling/3521-pod-scheduling-readiness.
|
||||||
|
//
|
||||||
|
// This is an alpha-level feature enabled by PodSchedulingReadiness feature gate.
|
||||||
|
// +optional
|
||||||
|
// +patchMergeKey=name
|
||||||
|
// +patchStrategy=merge
|
||||||
|
// +listType=map
|
||||||
|
// +listMapKey=name
|
||||||
|
SchedulingGates []PodSchedulingGate `json:"schedulingGates,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,38,opt,name=schedulingGates"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OSName is the set of OS'es that can be used in OS.
|
// OSName is the set of OS'es that can be used in OS.
|
||||||
@ -3347,6 +3361,13 @@ type PodOS struct {
|
|||||||
Name OSName `json:"name" protobuf:"bytes,1,opt,name=name"`
|
Name OSName `json:"name" protobuf:"bytes,1,opt,name=name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PodSchedulingGate is associated to a Pod to guard its scheduling.
|
||||||
|
type PodSchedulingGate struct {
|
||||||
|
// Name of the scheduling gate.
|
||||||
|
// Each scheduling gate must have a unique name field.
|
||||||
|
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
|
||||||
|
}
|
||||||
|
|
||||||
// +enum
|
// +enum
|
||||||
type UnsatisfiableConstraintAction string
|
type UnsatisfiableConstraintAction string
|
||||||
|
|
||||||
@ -4526,7 +4547,7 @@ type ServiceSpec struct {
|
|||||||
SessionAffinityConfig *SessionAffinityConfig `json:"sessionAffinityConfig,omitempty" protobuf:"bytes,14,opt,name=sessionAffinityConfig"`
|
SessionAffinityConfig *SessionAffinityConfig `json:"sessionAffinityConfig,omitempty" protobuf:"bytes,14,opt,name=sessionAffinityConfig"`
|
||||||
|
|
||||||
// TopologyKeys is tombstoned to show why 16 is reserved protobuf tag.
|
// TopologyKeys is tombstoned to show why 16 is reserved protobuf tag.
|
||||||
//TopologyKeys []string `json:"topologyKeys,omitempty" protobuf:"bytes,16,opt,name=topologyKeys"`
|
// TopologyKeys []string `json:"topologyKeys,omitempty" protobuf:"bytes,16,opt,name=topologyKeys"`
|
||||||
|
|
||||||
// IPFamily is tombstoned to show why 15 is a reserved protobuf tag.
|
// IPFamily is tombstoned to show why 15 is a reserved protobuf tag.
|
||||||
// IPFamily *IPFamily `json:"ipFamily,omitempty" protobuf:"bytes,15,opt,name=ipFamily,Configcasttype=IPFamily"`
|
// IPFamily *IPFamily `json:"ipFamily,omitempty" protobuf:"bytes,15,opt,name=ipFamily,Configcasttype=IPFamily"`
|
||||||
|
@ -184,6 +184,7 @@ INFO: Unexpected error: wait for pod pending-pod running:
|
|||||||
SetHostnameAsFQDN: nil,
|
SetHostnameAsFQDN: nil,
|
||||||
OS: nil,
|
OS: nil,
|
||||||
HostUsers: nil,
|
HostUsers: nil,
|
||||||
|
SchedulingGates: nil,
|
||||||
},
|
},
|
||||||
Status: {
|
Status: {
|
||||||
Phase: "",
|
Phase: "",
|
||||||
|
Loading…
Reference in New Issue
Block a user