Skip to content

Commit 851efa8

Browse files
authored
Merge pull request kubernetes#84051 from bart0sh/PR0079-multiple-sizes-hugepages
Implement support for multiple sizes huge pages
2 parents b5e95fc + 03ecc20 commit 851efa8

File tree

18 files changed

+955
-180
lines changed

18 files changed

+955
-180
lines changed

pkg/apis/core/types.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -583,9 +583,10 @@ type StorageMedium string
583583

584584
// These are the valid value for StorageMedium
585585
const (
586-
StorageMediumDefault StorageMedium = "" // use whatever the default is for the node
587-
StorageMediumMemory StorageMedium = "Memory" // use memory (tmpfs)
588-
StorageMediumHugePages StorageMedium = "HugePages" // use hugepages
586+
StorageMediumDefault StorageMedium = "" // use whatever the default is for the node
587+
StorageMediumMemory StorageMedium = "Memory" // use memory (tmpfs)
588+
StorageMediumHugePages StorageMedium = "HugePages" // use hugepages
589+
StorageMediumHugePagesPrefix StorageMedium = "HugePages-" // prefix for full medium notation HugePages-<size>
589590
)
590591

591592
// Protocol defines network protocols supported for things like container ports.

pkg/apis/core/v1/helper/helpers.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,27 @@ func HugePageUnitSizeFromByteSize(size int64) (string, error) {
103103
return fmt.Sprintf("%d%s", size, hugePageSizeUnitList[idx]), nil
104104
}
105105

106+
// IsHugePageMedium returns true if the volume medium is in 'HugePages[-size]' format
107+
func IsHugePageMedium(medium v1.StorageMedium) bool {
108+
if medium == v1.StorageMediumHugePages {
109+
return true
110+
}
111+
return strings.HasPrefix(string(medium), string(v1.StorageMediumHugePagesPrefix))
112+
}
113+
114+
// HugePageSizeFromMedium returns the page size for the specified huge page medium.
115+
// If the specified input is not a valid huge page medium an error is returned.
116+
func HugePageSizeFromMedium(medium v1.StorageMedium) (resource.Quantity, error) {
117+
if !IsHugePageMedium(medium) {
118+
return resource.Quantity{}, fmt.Errorf("medium: %s is not a hugepage medium", medium)
119+
}
120+
if medium == v1.StorageMediumHugePages {
121+
return resource.Quantity{}, fmt.Errorf("medium: %s doesn't have size information", medium)
122+
}
123+
pageSize := strings.TrimPrefix(string(medium), string(v1.StorageMediumHugePagesPrefix))
124+
return resource.ParseQuantity(pageSize)
125+
}
126+
106127
// IsOvercommitAllowed returns true if the resource is in the default
107128
// namespace and is not hugepages.
108129
func IsOvercommitAllowed(name v1.ResourceName) bool {

pkg/apis/core/v1/helper/helpers_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,73 @@ func TestHugePageSizeFromResourceName(t *testing.T) {
113113
}
114114
}
115115

116+
func TestHugePageSizeFromMedium(t *testing.T) {
117+
testCases := []struct {
118+
description string
119+
medium v1.StorageMedium
120+
expectVal resource.Quantity
121+
expectErr bool
122+
}{
123+
{
124+
description: "Invalid hugepages medium",
125+
medium: "Memory",
126+
expectVal: resource.Quantity{},
127+
expectErr: true,
128+
},
129+
{
130+
description: "Invalid hugepages medium",
131+
medium: "Memory",
132+
expectVal: resource.Quantity{},
133+
expectErr: true,
134+
},
135+
{
136+
description: "Invalid: HugePages without size",
137+
medium: "HugePages",
138+
expectVal: resource.Quantity{},
139+
expectErr: true,
140+
},
141+
{
142+
description: "Invalid: HugePages without size",
143+
medium: "HugePages",
144+
expectVal: resource.Quantity{},
145+
expectErr: true,
146+
},
147+
{
148+
description: "Valid: HugePages-1Gi",
149+
medium: "HugePages-1Gi",
150+
expectVal: resource.MustParse("1Gi"),
151+
expectErr: false,
152+
},
153+
{
154+
description: "Valid: HugePages-2Mi",
155+
medium: "HugePages-2Mi",
156+
expectVal: resource.MustParse("2Mi"),
157+
expectErr: false,
158+
},
159+
{
160+
description: "Valid: HugePages-64Ki",
161+
medium: "HugePages-64Ki",
162+
expectVal: resource.MustParse("64Ki"),
163+
expectErr: false,
164+
},
165+
}
166+
for i, tc := range testCases {
167+
t.Run(tc.description, func(t *testing.T) {
168+
t.Parallel()
169+
v, err := HugePageSizeFromMedium(tc.medium)
170+
if err == nil && tc.expectErr {
171+
t.Errorf("[%v]expected error but got none.", i)
172+
}
173+
if err != nil && !tc.expectErr {
174+
t.Errorf("[%v]did not expect error but got: %v", i, err)
175+
}
176+
if v != tc.expectVal {
177+
t.Errorf("Got %v but expected %v", v, tc.expectVal)
178+
}
179+
})
180+
}
181+
}
182+
116183
func TestIsOvercommitAllowed(t *testing.T) {
117184
testCases := []struct {
118185
resourceName v1.ResourceName

pkg/apis/core/validation/validation.go

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3083,8 +3083,33 @@ func validateContainersOnlyForPod(containers []core.Container, fldPath *field.Pa
30833083
return allErrs
30843084
}
30853085

3086+
// PodValidationOptions contains the different settings for pod validation
3087+
type PodValidationOptions struct {
3088+
// Allow pod spec to have more than one huge page resource (with different sizes)
3089+
AllowMultipleHugePageResources bool
3090+
}
3091+
3092+
// ValidatePodSingleHugePageResources checks if there are multiple huge
3093+
// pages resources in the pod object.
3094+
func ValidatePodSingleHugePageResources(pod *core.Pod, specPath *field.Path) field.ErrorList {
3095+
allErrs := field.ErrorList{}
3096+
hugePageResources := sets.NewString()
3097+
for i := range pod.Spec.Containers {
3098+
resourceSet := toContainerResourcesSet(&pod.Spec.Containers[i])
3099+
for resourceStr := range resourceSet {
3100+
if v1helper.IsHugePageResourceName(v1.ResourceName(resourceStr)) {
3101+
hugePageResources.Insert(resourceStr)
3102+
}
3103+
}
3104+
}
3105+
if len(hugePageResources) > 1 {
3106+
allErrs = append(allErrs, field.Invalid(specPath, hugePageResources.List(), "must use a single hugepage size in a pod spec"))
3107+
}
3108+
return allErrs
3109+
}
3110+
30863111
// ValidatePod tests if required fields in the pod are set.
3087-
func ValidatePod(pod *core.Pod) field.ErrorList {
3112+
func ValidatePod(pod *core.Pod, opts PodValidationOptions) field.ErrorList {
30883113
fldPath := field.NewPath("metadata")
30893114
allErrs := ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName, fldPath)
30903115
allErrs = append(allErrs, ValidatePodSpecificAnnotations(pod.ObjectMeta.Annotations, &pod.Spec, fldPath.Child("annotations"))...)
@@ -3111,17 +3136,8 @@ func ValidatePod(pod *core.Pod) field.ErrorList {
31113136
allErrs = append(allErrs, validateContainersOnlyForPod(pod.Spec.Containers, specPath.Child("containers"))...)
31123137
allErrs = append(allErrs, validateContainersOnlyForPod(pod.Spec.InitContainers, specPath.Child("initContainers"))...)
31133138

3114-
hugePageResources := sets.NewString()
3115-
for i := range pod.Spec.Containers {
3116-
resourceSet := toContainerResourcesSet(&pod.Spec.Containers[i])
3117-
for resourceStr := range resourceSet {
3118-
if v1helper.IsHugePageResourceName(v1.ResourceName(resourceStr)) {
3119-
hugePageResources.Insert(resourceStr)
3120-
}
3121-
}
3122-
}
3123-
if len(hugePageResources) > 1 {
3124-
allErrs = append(allErrs, field.Invalid(specPath, hugePageResources, "must use a single hugepage size in a pod spec"))
3139+
if !opts.AllowMultipleHugePageResources {
3140+
allErrs = append(allErrs, ValidatePodSingleHugePageResources(pod, specPath)...)
31253141
}
31263142

31273143
podIPsField := field.NewPath("status", "podIPs")
@@ -3679,8 +3695,8 @@ func ValidateContainerUpdates(newContainers, oldContainers []core.Container, fld
36793695
}
36803696

36813697
// ValidatePodCreate validates a pod in the context of its initial create
3682-
func ValidatePodCreate(pod *core.Pod) field.ErrorList {
3683-
allErrs := ValidatePod(pod)
3698+
func ValidatePodCreate(pod *core.Pod, opts PodValidationOptions) field.ErrorList {
3699+
allErrs := ValidatePod(pod, opts)
36843700

36853701
fldPath := field.NewPath("spec")
36863702
// EphemeralContainers can only be set on update using the ephemeralcontainers subresource
@@ -3693,12 +3709,16 @@ func ValidatePodCreate(pod *core.Pod) field.ErrorList {
36933709

36943710
// ValidatePodUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields
36953711
// that cannot be changed.
3696-
func ValidatePodUpdate(newPod, oldPod *core.Pod) field.ErrorList {
3712+
func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) field.ErrorList {
36973713
fldPath := field.NewPath("metadata")
36983714
allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath)
36993715
allErrs = append(allErrs, ValidatePodSpecificAnnotationUpdates(newPod, oldPod, fldPath.Child("annotations"))...)
37003716
specPath := field.NewPath("spec")
37013717

3718+
if !opts.AllowMultipleHugePageResources {
3719+
allErrs = append(allErrs, ValidatePodSingleHugePageResources(newPod, specPath)...)
3720+
}
3721+
37023722
// validate updateable fields:
37033723
// 1. spec.containers[*].image
37043724
// 2. spec.initContainers[*].image

0 commit comments

Comments
 (0)