From e162fcc1bf56e2eb7e978dc411dfc370c1d95c5a Mon Sep 17 00:00:00 2001 From: lala123912 Date: Thu, 28 Jan 2021 15:32:51 +0800 Subject: [PATCH] Add request value verification for hugepage --- pkg/api/pod/util.go | 52 +++++ pkg/apis/core/helper/helpers.go | 15 ++ pkg/apis/core/helper/helpers_test.go | 54 +++++ pkg/apis/core/validation/validation.go | 30 ++- pkg/apis/core/validation/validation_test.go | 89 ++++++++- pkg/apis/node/validation/validation.go | 3 +- pkg/registry/apps/deployment/strategy_test.go | 112 +++++++++++ pkg/registry/core/pod/strategy_test.go | 184 ++++++++++++++++++ 8 files changed, 530 insertions(+), 9 deletions(-) diff --git a/pkg/api/pod/util.go b/pkg/api/pod/util.go index d8e7a1a42a6f..59f1a198e75b 100644 --- a/pkg/api/pod/util.go +++ b/pkg/api/pod/util.go @@ -345,6 +345,52 @@ func usesMultipleHugePageResources(podSpec *api.PodSpec) bool { return len(hugePageResources) > 1 } +func checkContainerUseIndivisibleHugePagesValues(container api.Container) bool { + for resourceName, quantity := range container.Resources.Limits { + if helper.IsHugePageResourceName(resourceName) { + if !helper.IsHugePageResourceValueDivisible(resourceName, quantity) { + return true + } + } + } + + for resourceName, quantity := range container.Resources.Requests { + if helper.IsHugePageResourceName(resourceName) { + if !helper.IsHugePageResourceValueDivisible(resourceName, quantity) { + return true + } + } + } + + return false +} + +// usesIndivisibleHugePagesValues returns true if the one of the containers uses non-integer multiple +// of huge page unit size +func usesIndivisibleHugePagesValues(podSpec *api.PodSpec) bool { + foundIndivisibleHugePagesValue := false + VisitContainers(podSpec, AllContainers, func(c *api.Container, containerType ContainerType) bool { + if checkContainerUseIndivisibleHugePagesValues(*c) { + foundIndivisibleHugePagesValue = true + } + return !foundIndivisibleHugePagesValue // continue visiting if we haven't seen an invalid value yet + }) + + if foundIndivisibleHugePagesValue { + return true + } + + for resourceName, quantity := range podSpec.Overhead { + if helper.IsHugePageResourceName(resourceName) { + if !helper.IsHugePageResourceValueDivisible(resourceName, quantity) { + return true + } + } + } + + return false +} + // GetValidationOptionsFromPodSpecAndMeta returns validation options based on pod specs and metadata func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, podMeta, oldPodMeta *metav1.ObjectMeta) apivalidation.PodValidationOptions { // default pod validation options based on feature gate @@ -354,6 +400,8 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po // Allow pod spec to use hugepages in downward API if feature is enabled AllowDownwardAPIHugePages: utilfeature.DefaultFeatureGate.Enabled(features.DownwardAPIHugePages), AllowInvalidPodDeletionCost: !utilfeature.DefaultFeatureGate.Enabled(features.PodDeletionCost), + // Do not allow pod spec to use non-integer multiple of huge page unit size default + AllowIndivisibleHugePagesValues: false, } if oldPodSpec != nil { @@ -368,12 +416,16 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po return !opts.AllowDownwardAPIHugePages }) } + + // if old spec used non-integer multiple of huge page unit size, we must allow it + opts.AllowIndivisibleHugePagesValues = usesIndivisibleHugePagesValues(oldPodSpec) } if oldPodMeta != nil && !opts.AllowInvalidPodDeletionCost { // This is an update, so validate only if the existing object was valid. _, err := helper.GetDeletionCostFromPodAnnotations(oldPodMeta.Annotations) opts.AllowInvalidPodDeletionCost = err != nil } + return opts } diff --git a/pkg/apis/core/helper/helpers.go b/pkg/apis/core/helper/helpers.go index 8d6e27f57db2..3dbb49993ea6 100644 --- a/pkg/apis/core/helper/helpers.go +++ b/pkg/apis/core/helper/helpers.go @@ -39,6 +39,21 @@ func IsHugePageResourceName(name core.ResourceName) bool { return strings.HasPrefix(string(name), core.ResourceHugePagesPrefix) } +// IsHugePageResourceValueDivisible returns true if the resource value of storage is +// integer multiple of page size. +func IsHugePageResourceValueDivisible(name core.ResourceName, quantity resource.Quantity) bool { + pageSize, err := HugePageSizeFromResourceName(name) + if err != nil { + return false + } + + if pageSize.Sign() <= 0 || pageSize.MilliValue()%int64(1000) != int64(0) { + return false + } + + return quantity.Value()%pageSize.Value() == 0 +} + // IsQuotaHugePageResourceName returns true if the resource name has the quota // related huge page resource prefix. func IsQuotaHugePageResourceName(name core.ResourceName) bool { diff --git a/pkg/apis/core/helper/helpers_test.go b/pkg/apis/core/helper/helpers_test.go index 0d197651cfce..6b95876d94e1 100644 --- a/pkg/apis/core/helper/helpers_test.go +++ b/pkg/apis/core/helper/helpers_test.go @@ -211,6 +211,60 @@ func TestIsHugePageResourceName(t *testing.T) { } } +func TestIsHugePageResourceValueDivisible(t *testing.T) { + testCases := []struct { + name core.ResourceName + quantity resource.Quantity + result bool + }{ + { + name: core.ResourceName("hugepages-2Mi"), + quantity: resource.MustParse("4Mi"), + result: true, + }, + { + name: core.ResourceName("hugepages-2Mi"), + quantity: resource.MustParse("5Mi"), + result: false, + }, + { + name: core.ResourceName("hugepages-1Gi"), + quantity: resource.MustParse("2Gi"), + result: true, + }, + { + name: core.ResourceName("hugepages-1Gi"), + quantity: resource.MustParse("2.1Gi"), + result: false, + }, + { + name: core.ResourceName("hugepages-1Mi"), + quantity: resource.MustParse("2.1Mi"), + result: false, + }, + { + name: core.ResourceName("hugepages-64Ki"), + quantity: resource.MustParse("128Ki"), + result: true, + }, + { + name: core.ResourceName("hugepages-"), + quantity: resource.MustParse("128Ki"), + result: false, + }, + { + name: core.ResourceName("hugepages"), + quantity: resource.MustParse("128Ki"), + result: false, + }, + } + for _, testCase := range testCases { + if testCase.result != IsHugePageResourceValueDivisible(testCase.name, testCase.quantity) { + t.Errorf("resource: %v storage:%v expected result: %v", testCase.name, testCase.quantity, testCase.result) + } + } +} + func TestHugePageResourceName(t *testing.T) { testCases := []struct { pageSize resource.Quantity diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go index 300b9cb1be80..e9aadc0d794d 100644 --- a/pkg/apis/core/validation/validation.go +++ b/pkg/apis/core/validation/validation.go @@ -292,9 +292,9 @@ func ValidateRuntimeClassName(name string, fldPath *field.Path) field.ErrorList } // validateOverhead can be used to check whether the given Overhead is valid. -func validateOverhead(overhead core.ResourceList, fldPath *field.Path) field.ErrorList { +func validateOverhead(overhead core.ResourceList, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { // reuse the ResourceRequirements validation logic - return ValidateResourceRequirements(&core.ResourceRequirements{Limits: overhead}, fldPath) + return ValidateResourceRequirements(&core.ResourceRequirements{Limits: overhead}, fldPath, opts) } // Validates that given value is not negative. @@ -2880,7 +2880,7 @@ func validateContainers(containers []core.Container, isInitContainers bool, volu allErrs = append(allErrs, ValidateVolumeMounts(ctr.VolumeMounts, volDevices, volumes, &ctr, idxPath.Child("volumeMounts"))...) allErrs = append(allErrs, ValidateVolumeDevices(ctr.VolumeDevices, volMounts, volumes, idxPath.Child("volumeDevices"))...) allErrs = append(allErrs, validatePullPolicy(ctr.ImagePullPolicy, idxPath.Child("imagePullPolicy"))...) - allErrs = append(allErrs, ValidateResourceRequirements(&ctr.Resources, idxPath.Child("resources"))...) + allErrs = append(allErrs, ValidateResourceRequirements(&ctr.Resources, idxPath.Child("resources"), opts)...) allErrs = append(allErrs, ValidateSecurityContext(ctr.SecurityContext, idxPath.Child("securityContext"))...) } @@ -3193,6 +3193,8 @@ type PodValidationOptions struct { AllowDownwardAPIHugePages bool // Allow invalid pod-deletion-cost annotation value for backward compatibility. AllowInvalidPodDeletionCost bool + // Allow pod spec to use non-integer multiple of huge page unit size + AllowIndivisibleHugePagesValues bool } // ValidatePodSingleHugePageResources checks if there are multiple huge @@ -3366,7 +3368,7 @@ func ValidatePodSpec(spec *core.PodSpec, podMeta *metav1.ObjectMeta, fldPath *fi } if spec.Overhead != nil { - allErrs = append(allErrs, validateOverhead(spec.Overhead, fldPath.Child("overhead"))...) + allErrs = append(allErrs, validateOverhead(spec.Overhead, fldPath.Child("overhead"), opts)...) } return allErrs @@ -5321,7 +5323,7 @@ func validateBasicResource(quantity resource.Quantity, fldPath *field.Path) fiel } // Validates resource requirement spec. -func ValidateResourceRequirements(requirements *core.ResourceRequirements, fldPath *field.Path) field.ErrorList { +func ValidateResourceRequirements(requirements *core.ResourceRequirements, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { allErrs := field.ErrorList{} limPath := fldPath.Child("limits") reqPath := fldPath.Child("requests") @@ -5341,6 +5343,9 @@ func ValidateResourceRequirements(requirements *core.ResourceRequirements, fldPa if helper.IsHugePageResourceName(resourceName) { limContainsHugePages = true + if err := validateResourceQuantityHugePageValue(resourceName, quantity, opts); err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, quantity.String(), err.Error())) + } } if supportedQoSComputeResources.Has(string(resourceName)) { @@ -5368,6 +5373,9 @@ func ValidateResourceRequirements(requirements *core.ResourceRequirements, fldPa } if helper.IsHugePageResourceName(resourceName) { reqContainsHugePages = true + if err := validateResourceQuantityHugePageValue(resourceName, quantity, opts); err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, quantity.String(), err.Error())) + } } if supportedQoSComputeResources.Has(string(resourceName)) { reqContainsCPUOrMemory = true @@ -5381,6 +5389,18 @@ func ValidateResourceRequirements(requirements *core.ResourceRequirements, fldPa return allErrs } +func validateResourceQuantityHugePageValue(name core.ResourceName, quantity resource.Quantity, opts PodValidationOptions) error { + if !helper.IsHugePageResourceName(name) { + return nil + } + + if !opts.AllowIndivisibleHugePagesValues && !helper.IsHugePageResourceValueDivisible(name, quantity) { + return fmt.Errorf("%s is not positive integer multiple of %s", quantity.String(), name) + } + + return nil +} + // validateResourceQuotaScopes ensures that each enumerated hard resource constraint is valid for set of scopes func validateResourceQuotaScopes(resourceQuotaSpec *core.ResourceQuotaSpec, opts ResourceQuotaValidationOptions, fld *field.Path) field.ErrorList { allErrs := field.ErrorList{} diff --git a/pkg/apis/core/validation/validation_test.go b/pkg/apis/core/validation/validation_test.go index 062cbb84ec23..755e35ad346e 100644 --- a/pkg/apis/core/validation/validation_test.go +++ b/pkg/apis/core/validation/validation_test.go @@ -4446,7 +4446,7 @@ func TestAlphaLocalStorageCapacityIsolation(t *testing.T) { resource.BinarySI), }, } - if errs := ValidateResourceRequirements(&containerLimitCase, field.NewPath("resources")); len(errs) != 0 { + if errs := ValidateResourceRequirements(&containerLimitCase, field.NewPath("resources"), PodValidationOptions{}); len(errs) != 0 { t.Errorf("expected success: %v", errs) } } @@ -16410,7 +16410,7 @@ func TestValidateOverhead(t *testing.T) { }, } for _, tc := range successCase { - if errs := validateOverhead(tc.overhead, field.NewPath("overheads")); len(errs) != 0 { + if errs := validateOverhead(tc.overhead, field.NewPath("overheads"), PodValidationOptions{}); len(errs) != 0 { t.Errorf("%q unexpected error: %v", tc.Name, errs) } } @@ -16427,7 +16427,7 @@ func TestValidateOverhead(t *testing.T) { }, } for _, tc := range errorCase { - if errs := validateOverhead(tc.overhead, field.NewPath("resources")); len(errs) == 0 { + if errs := validateOverhead(tc.overhead, field.NewPath("resources"), PodValidationOptions{}); len(errs) == 0 { t.Errorf("%q expected error", tc.Name) } } @@ -17087,3 +17087,86 @@ func TestValidatePodTemplateSpecSeccomp(t *testing.T) { asserttestify.Equal(t, test.expectedErr, err, "TestCase[%d]: %s", i, test.description) } } + +func TestValidateResourceRequirements(t *testing.T) { + path := field.NewPath("resources") + tests := []struct { + name string + requirements core.ResourceRequirements + opts PodValidationOptions + }{ + { + name: "limits and requests of hugepage resource are equal", + requirements: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceCPU: resource.MustParse("10"), + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), + }, + Requests: core.ResourceList{ + core.ResourceCPU: resource.MustParse("10"), + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), + }, + }, + opts: PodValidationOptions{}, + }, + { + name: "limits and requests of memory resource are equal", + requirements: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceMemory: resource.MustParse("2Mi"), + }, + Requests: core.ResourceList{ + core.ResourceMemory: resource.MustParse("2Mi"), + }, + }, + opts: PodValidationOptions{}, + }, + { + name: "limits and requests of cpu resource are equal", + requirements: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceCPU: resource.MustParse("10"), + }, + Requests: core.ResourceList{ + core.ResourceCPU: resource.MustParse("10"), + }, + }, + opts: PodValidationOptions{}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if errs := ValidateResourceRequirements(&tc.requirements, path, tc.opts); len(errs) != 0 { + t.Errorf("unexpected errors: %v", errs) + } + }) + } + + errTests := []struct { + name string + requirements core.ResourceRequirements + opts PodValidationOptions + }{ + { + name: "hugepage resource without cpu or memory", + requirements: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), + }, + Requests: core.ResourceList{ + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), + }, + }, + opts: PodValidationOptions{}, + }, + } + + for _, tc := range errTests { + t.Run(tc.name, func(t *testing.T) { + if errs := ValidateResourceRequirements(&tc.requirements, path, tc.opts); len(errs) == 0 { + t.Error("expected errors") + } + }) + } +} diff --git a/pkg/apis/node/validation/validation.go b/pkg/apis/node/validation/validation.go index 3f9ce994f4cd..60f6161611f9 100644 --- a/pkg/apis/node/validation/validation.go +++ b/pkg/apis/node/validation/validation.go @@ -54,7 +54,8 @@ func ValidateRuntimeClassUpdate(new, old *node.RuntimeClass) field.ErrorList { func validateOverhead(overhead *node.Overhead, fldPath *field.Path) field.ErrorList { // reuse the ResourceRequirements validation logic - return corevalidation.ValidateResourceRequirements(&core.ResourceRequirements{Limits: overhead.PodFixed}, fldPath) + return corevalidation.ValidateResourceRequirements(&core.ResourceRequirements{Limits: overhead.PodFixed}, fldPath, + corevalidation.PodValidationOptions{}) } func validateScheduling(s *node.Scheduling, fldPath *field.Path) field.ErrorList { diff --git a/pkg/registry/apps/deployment/strategy_test.go b/pkg/registry/apps/deployment/strategy_test.go index 3376993605ab..8955cb5754bc 100644 --- a/pkg/registry/apps/deployment/strategy_test.go +++ b/pkg/registry/apps/deployment/strategy_test.go @@ -20,6 +20,7 @@ import ( "reflect" "testing" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" @@ -247,3 +248,114 @@ func TestDeploymentDefaultGarbageCollectionPolicy(t *testing.T) { } } } + +func newDeploymentWithHugePageValue(reousreceName api.ResourceName, value resource.Quantity) *apps.Deployment { + return &apps.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: deploymentName, + Namespace: namespace, + ResourceVersion: "1", + }, + Spec: apps.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"foo": "bar"}, + MatchExpressions: []metav1.LabelSelectorRequirement{}, + }, + Strategy: apps.DeploymentStrategy{ + Type: apps.RollingUpdateDeploymentStrategyType, + RollingUpdate: &apps.RollingUpdateDeployment{ + MaxSurge: intstr.FromInt(1), + MaxUnavailable: intstr.FromInt(1), + }, + }, + Template: api.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "foo", + Labels: map[string]string{"foo": "bar"}, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSDefault, + Containers: []api.Container{{ + Name: fakeImageName, + Image: fakeImage, + ImagePullPolicy: api.PullNever, + TerminationMessagePolicy: api.TerminationMessageReadFile, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(reousreceName): value, + }, + Limits: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(reousreceName): value, + }, + }}, + }}, + }, + }, + } +} + +func TestDeploymentStrategyValidate(t *testing.T) { + tests := []struct { + name string + deployment *apps.Deployment + }{ + { + name: "validation on a new deployment with indivisible hugepages values", + deployment: newDeploymentWithHugePageValue(api.ResourceHugePagesPrefix+"2Mi", resource.MustParse("2.1Mi")), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if errs := Strategy.Validate(genericapirequest.NewContext(), tc.deployment); len(errs) == 0 { + t.Error("expected failure") + } + }) + } +} + +func TestDeploymentStrategyValidateUpdate(t *testing.T) { + tests := []struct { + name string + newDeployment *apps.Deployment + oldDeployment *apps.Deployment + }{ + { + name: "validation on an existing deployment with indivisible hugepages values to a new deployment with indivisible hugepages values", + newDeployment: newDeploymentWithHugePageValue(api.ResourceHugePagesPrefix+"2Mi", resource.MustParse("2.1Mi")), + oldDeployment: newDeploymentWithHugePageValue(api.ResourceHugePagesPrefix+"1Gi", resource.MustParse("1.1Gi")), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if errs := Strategy.ValidateUpdate(genericapirequest.NewContext(), tc.newDeployment, tc.oldDeployment); len(errs) != 0 { + t.Errorf("unexpected error:%v", errs) + } + }) + } + + errTests := []struct { + name string + newDeployment *apps.Deployment + oldDeployment *apps.Deployment + }{ + { + name: "validation on an existing deployment with divisible hugepages values to a new deployment with indivisible hugepages values", + newDeployment: newDeploymentWithHugePageValue(api.ResourceHugePagesPrefix+"2Mi", resource.MustParse("2.1Mi")), + oldDeployment: newDeploymentWithHugePageValue(api.ResourceHugePagesPrefix+"1Gi", resource.MustParse("2Gi")), + }, + } + + for _, tc := range errTests { + t.Run(tc.name, func(t *testing.T) { + if errs := Strategy.ValidateUpdate(genericapirequest.NewContext(), tc.newDeployment, tc.oldDeployment); len(errs) == 0 { + t.Error("expected failure") + } + }) + } +} diff --git a/pkg/registry/core/pod/strategy_test.go b/pkg/registry/core/pod/strategy_test.go index 77616b9f3412..bc2bf1b35281 100644 --- a/pkg/registry/core/pod/strategy_test.go +++ b/pkg/registry/core/pod/strategy_test.go @@ -1197,3 +1197,187 @@ func createPodWithGenericEphemeralVolume() *api.Pod { }, } } + +func newPodtWithHugePageValue(reousreceName api.ResourceName, value resource.Quantity) *api.Pod { + return &api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "foo", + ResourceVersion: "1", + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSDefault, + Containers: []api.Container{{ + Name: "foo", + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceCPU: resource.MustParse("10"), + reousreceName: value, + }, + Limits: api.ResourceList{ + api.ResourceCPU: resource.MustParse("10"), + reousreceName: value, + }, + }}, + }, + }, + } +} + +func TestPodStrategyValidate(t *testing.T) { + const containerName = "container" + errTest := []struct { + name string + pod *api.Pod + }{ + { + name: "a new pod setting container with indivisible hugepages values", + pod: newPodtWithHugePageValue(api.ResourceHugePagesPrefix+"1Mi", resource.MustParse("1.1Mi")), + }, + { + name: "a new pod setting init-container with indivisible hugepages values", + pod: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "foo", + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSDefault, + InitContainers: []api.Container{{ + Name: containerName, + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceHugePagesPrefix + "64Ki"): resource.MustParse("127Ki"), + }, + Limits: api.ResourceList{ + api.ResourceName(api.ResourceHugePagesPrefix + "64Ki"): resource.MustParse("127Ki"), + }, + }}, + }, + }, + }, + }, + { + name: "a new pod setting init-container with indivisible hugepages values while container with divisible hugepages values", + pod: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "foo", + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSDefault, + InitContainers: []api.Container{{ + Name: containerName, + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("5.1Mi"), + }, + Limits: api.ResourceList{ + api.ResourceName(api.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("5.1Mi"), + }, + }}, + }, + Containers: []api.Container{{ + Name: containerName, + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceHugePagesPrefix + "1Gi"): resource.MustParse("2Gi"), + }, + Limits: api.ResourceList{ + api.ResourceName(api.ResourceHugePagesPrefix + "1Gi"): resource.MustParse("2Gi"), + }, + }}, + }, + }, + }, + }, + } + + for _, tc := range errTest { + t.Run(tc.name, func(t *testing.T) { + if errs := Strategy.Validate(genericapirequest.NewContext(), tc.pod); len(errs) == 0 { + t.Error("expected failure") + } + }) + } + + tests := []struct { + name string + pod *api.Pod + }{ + { + name: "a new pod setting container with divisible hugepages values", + pod: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "foo", + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSDefault, + Containers: []api.Container{{ + Name: containerName, + Image: "image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePolicy: "File", + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceHugePagesPrefix + "1Mi"): resource.MustParse("2Mi"), + }, + Limits: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceHugePagesPrefix + "1Mi"): resource.MustParse("2Mi"), + }, + }}, + }, + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if errs := Strategy.Validate(genericapirequest.NewContext(), tc.pod); len(errs) != 0 { + t.Errorf("unexpected error:%v", errs) + } + }) + } +} + +func TestPodStrategyValidateUpdate(t *testing.T) { + test := []struct { + name string + newPod *api.Pod + oldPod *api.Pod + }{ + { + name: "an existing pod with indivisible hugepages values to a new pod with indivisible hugepages values", + newPod: newPodtWithHugePageValue(api.ResourceHugePagesPrefix+"2Mi", resource.MustParse("2.1Mi")), + oldPod: newPodtWithHugePageValue(api.ResourceHugePagesPrefix+"2Mi", resource.MustParse("2.1Mi")), + }, + } + + for _, tc := range test { + t.Run(tc.name, func(t *testing.T) { + if errs := Strategy.ValidateUpdate(genericapirequest.NewContext(), tc.newPod, tc.oldPod); len(errs) != 0 { + t.Errorf("unexpected error:%v", errs) + } + }) + } +}