diff --git a/pkg/apis/core/types.go b/pkg/apis/core/types.go index 497b9924c63..fca2f52fac3 100644 --- a/pkg/apis/core/types.go +++ b/pkg/apis/core/types.go @@ -2700,6 +2700,16 @@ type PodSpec struct { // This is a beta feature as of Kubernetes v1.14. // +optional RuntimeClassName *string + // Overhead represents the resource overhead associated with running a pod for a given RuntimeClass. + // This field will be autopopulated at admission time by the RuntimeClass admission controller. If + // the RuntimeClass admission controller is enabled, overhead must not be set in Pod create requests. + // The RuntimeClass admission controller will reject Pod create requests which have the overhead already + // set. If RuntimeClass is configured and selected in the PodSpec, Overhead will be set to the value + // defined in the corresponding RuntimeClass, otherwise it will remain unset and treated as zero. + // More info: https://git.k8s.io/enhancements/keps/sig-node/20190226-pod-overhead.md + // This field is alpha-level as of Kubernetes v1.16, and is only honored by servers that enable the PodOverhead feature. + // +optional + Overhead ResourceList // EnableServiceLinks indicates whether information about services should be injected into pod's // environment variables, matching the syntax of Docker links. // If not specified, the default is true. diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go index 1ff6be313e6..6bbc1d1e031 100644 --- a/pkg/apis/core/validation/validation.go +++ b/pkg/apis/core/validation/validation.go @@ -264,6 +264,12 @@ func ValidateRuntimeClassName(name string, fldPath *field.Path) field.ErrorList return allErrs } +// validateOverhead can be used to check whether the given Overhead is valid. +func validateOverhead(overhead core.ResourceList, fldPath *field.Path) field.ErrorList { + // reuse the ResourceRequirements validation logic + return ValidateResourceRequirements(&core.ResourceRequirements{Limits: overhead}, fldPath) +} + // Validates that given value is not negative. func ValidateNonnegativeField(value int64, fldPath *field.Path) field.ErrorList { return apimachineryvalidation.ValidateNonnegativeField(value, fldPath) @@ -3095,6 +3101,10 @@ func ValidatePodSpec(spec *core.PodSpec, fldPath *field.Path) field.ErrorList { allErrs = append(allErrs, ValidatePreemptionPolicy(spec.PreemptionPolicy, fldPath.Child("preemptionPolicy"))...) } + if spec.Overhead != nil && utilfeature.DefaultFeatureGate.Enabled(features.PodOverhead) { + allErrs = append(allErrs, validateOverhead(spec.Overhead, fldPath.Child("overhead"))...) + } + return allErrs } diff --git a/pkg/apis/core/validation/validation_test.go b/pkg/apis/core/validation/validation_test.go index 9d0bbc7ad68..6617b6ac6b1 100644 --- a/pkg/apis/core/validation/validation_test.go +++ b/pkg/apis/core/validation/validation_test.go @@ -6334,6 +6334,7 @@ func TestValidatePodSpec(t *testing.T) { defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodPriority, true)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RuntimeClass, true)() + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodOverhead, true)() successCases := []core.PodSpec{ { // Populate basic fields, leave defaults for most. @@ -6474,6 +6475,13 @@ func TestValidatePodSpec(t *testing.T) { DNSPolicy: core.DNSClusterFirst, RuntimeClassName: utilpointer.StringPtr("valid-sandbox"), }, + { // Populate Overhead + Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + RestartPolicy: core.RestartPolicyAlways, + DNSPolicy: core.DNSClusterFirst, + RuntimeClassName: utilpointer.StringPtr("valid-sandbox"), + Overhead: core.ResourceList{}, + }, } for i := range successCases { if errs := ValidatePodSpec(&successCases[i], field.NewPath("field")); len(errs) != 0 { @@ -13498,3 +13506,42 @@ func TestAlphaVolumePVCDataSource(t *testing.T) { } } } + +func TestValidateOverhead(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodOverhead, true)() + + successCase := []struct { + Name string + overhead core.ResourceList + }{ + { + Name: "Valid Overhead for CPU + Memory", + overhead: core.ResourceList{ + core.ResourceName(core.ResourceCPU): resource.MustParse("10"), + core.ResourceName(core.ResourceMemory): resource.MustParse("10G"), + }, + }, + } + for _, tc := range successCase { + if errs := validateOverhead(tc.overhead, field.NewPath("overheads")); len(errs) != 0 { + t.Errorf("%q unexpected error: %v", tc.Name, errs) + } + } + + errorCase := []struct { + Name string + overhead core.ResourceList + }{ + { + Name: "Invalid Overhead Resources", + overhead: core.ResourceList{ + core.ResourceName("my.org"): resource.MustParse("10m"), + }, + }, + } + for _, tc := range errorCase { + if errs := validateOverhead(tc.overhead, field.NewPath("resources")); len(errs) == 0 { + t.Errorf("%q expected error", tc.Name) + } + } +}