mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 20:53:33 +00:00
Merge pull request #84051 from bart0sh/PR0079-multiple-sizes-hugepages
Implement support for multiple sizes huge pages
This commit is contained in:
commit
851efa8a34
@ -583,9 +583,10 @@ type StorageMedium string
|
|||||||
|
|
||||||
// These are the valid value for StorageMedium
|
// These are the valid value for StorageMedium
|
||||||
const (
|
const (
|
||||||
StorageMediumDefault StorageMedium = "" // use whatever the default is for the node
|
StorageMediumDefault StorageMedium = "" // use whatever the default is for the node
|
||||||
StorageMediumMemory StorageMedium = "Memory" // use memory (tmpfs)
|
StorageMediumMemory StorageMedium = "Memory" // use memory (tmpfs)
|
||||||
StorageMediumHugePages StorageMedium = "HugePages" // use hugepages
|
StorageMediumHugePages StorageMedium = "HugePages" // use hugepages
|
||||||
|
StorageMediumHugePagesPrefix StorageMedium = "HugePages-" // prefix for full medium notation HugePages-<size>
|
||||||
)
|
)
|
||||||
|
|
||||||
// Protocol defines network protocols supported for things like container ports.
|
// Protocol defines network protocols supported for things like container ports.
|
||||||
|
@ -103,6 +103,27 @@ func HugePageUnitSizeFromByteSize(size int64) (string, error) {
|
|||||||
return fmt.Sprintf("%d%s", size, hugePageSizeUnitList[idx]), nil
|
return fmt.Sprintf("%d%s", size, hugePageSizeUnitList[idx]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsHugePageMedium returns true if the volume medium is in 'HugePages[-size]' format
|
||||||
|
func IsHugePageMedium(medium v1.StorageMedium) bool {
|
||||||
|
if medium == v1.StorageMediumHugePages {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return strings.HasPrefix(string(medium), string(v1.StorageMediumHugePagesPrefix))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HugePageSizeFromMedium returns the page size for the specified huge page medium.
|
||||||
|
// If the specified input is not a valid huge page medium an error is returned.
|
||||||
|
func HugePageSizeFromMedium(medium v1.StorageMedium) (resource.Quantity, error) {
|
||||||
|
if !IsHugePageMedium(medium) {
|
||||||
|
return resource.Quantity{}, fmt.Errorf("medium: %s is not a hugepage medium", medium)
|
||||||
|
}
|
||||||
|
if medium == v1.StorageMediumHugePages {
|
||||||
|
return resource.Quantity{}, fmt.Errorf("medium: %s doesn't have size information", medium)
|
||||||
|
}
|
||||||
|
pageSize := strings.TrimPrefix(string(medium), string(v1.StorageMediumHugePagesPrefix))
|
||||||
|
return resource.ParseQuantity(pageSize)
|
||||||
|
}
|
||||||
|
|
||||||
// IsOvercommitAllowed returns true if the resource is in the default
|
// IsOvercommitAllowed returns true if the resource is in the default
|
||||||
// namespace and is not hugepages.
|
// namespace and is not hugepages.
|
||||||
func IsOvercommitAllowed(name v1.ResourceName) bool {
|
func IsOvercommitAllowed(name v1.ResourceName) bool {
|
||||||
|
@ -113,6 +113,73 @@ func TestHugePageSizeFromResourceName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHugePageSizeFromMedium(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
description string
|
||||||
|
medium v1.StorageMedium
|
||||||
|
expectVal resource.Quantity
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "Invalid hugepages medium",
|
||||||
|
medium: "Memory",
|
||||||
|
expectVal: resource.Quantity{},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Invalid hugepages medium",
|
||||||
|
medium: "Memory",
|
||||||
|
expectVal: resource.Quantity{},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Invalid: HugePages without size",
|
||||||
|
medium: "HugePages",
|
||||||
|
expectVal: resource.Quantity{},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Invalid: HugePages without size",
|
||||||
|
medium: "HugePages",
|
||||||
|
expectVal: resource.Quantity{},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Valid: HugePages-1Gi",
|
||||||
|
medium: "HugePages-1Gi",
|
||||||
|
expectVal: resource.MustParse("1Gi"),
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Valid: HugePages-2Mi",
|
||||||
|
medium: "HugePages-2Mi",
|
||||||
|
expectVal: resource.MustParse("2Mi"),
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Valid: HugePages-64Ki",
|
||||||
|
medium: "HugePages-64Ki",
|
||||||
|
expectVal: resource.MustParse("64Ki"),
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, tc := range testCases {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
v, err := HugePageSizeFromMedium(tc.medium)
|
||||||
|
if err == nil && tc.expectErr {
|
||||||
|
t.Errorf("[%v]expected error but got none.", i)
|
||||||
|
}
|
||||||
|
if err != nil && !tc.expectErr {
|
||||||
|
t.Errorf("[%v]did not expect error but got: %v", i, err)
|
||||||
|
}
|
||||||
|
if v != tc.expectVal {
|
||||||
|
t.Errorf("Got %v but expected %v", v, tc.expectVal)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestIsOvercommitAllowed(t *testing.T) {
|
func TestIsOvercommitAllowed(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
resourceName v1.ResourceName
|
resourceName v1.ResourceName
|
||||||
|
@ -3083,8 +3083,33 @@ func validateContainersOnlyForPod(containers []core.Container, fldPath *field.Pa
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PodValidationOptions contains the different settings for pod validation
|
||||||
|
type PodValidationOptions struct {
|
||||||
|
// Allow pod spec to have more than one huge page resource (with different sizes)
|
||||||
|
AllowMultipleHugePageResources bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidatePodSingleHugePageResources checks if there are multiple huge
|
||||||
|
// pages resources in the pod object.
|
||||||
|
func ValidatePodSingleHugePageResources(pod *core.Pod, specPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
hugePageResources := sets.NewString()
|
||||||
|
for i := range pod.Spec.Containers {
|
||||||
|
resourceSet := toContainerResourcesSet(&pod.Spec.Containers[i])
|
||||||
|
for resourceStr := range resourceSet {
|
||||||
|
if v1helper.IsHugePageResourceName(v1.ResourceName(resourceStr)) {
|
||||||
|
hugePageResources.Insert(resourceStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(hugePageResources) > 1 {
|
||||||
|
allErrs = append(allErrs, field.Invalid(specPath, hugePageResources.List(), "must use a single hugepage size in a pod spec"))
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
// ValidatePod tests if required fields in the pod are set.
|
// ValidatePod tests if required fields in the pod are set.
|
||||||
func ValidatePod(pod *core.Pod) field.ErrorList {
|
func ValidatePod(pod *core.Pod, opts PodValidationOptions) field.ErrorList {
|
||||||
fldPath := field.NewPath("metadata")
|
fldPath := field.NewPath("metadata")
|
||||||
allErrs := ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName, fldPath)
|
allErrs := ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName, fldPath)
|
||||||
allErrs = append(allErrs, ValidatePodSpecificAnnotations(pod.ObjectMeta.Annotations, &pod.Spec, fldPath.Child("annotations"))...)
|
allErrs = append(allErrs, ValidatePodSpecificAnnotations(pod.ObjectMeta.Annotations, &pod.Spec, fldPath.Child("annotations"))...)
|
||||||
@ -3111,17 +3136,8 @@ func ValidatePod(pod *core.Pod) field.ErrorList {
|
|||||||
allErrs = append(allErrs, validateContainersOnlyForPod(pod.Spec.Containers, specPath.Child("containers"))...)
|
allErrs = append(allErrs, validateContainersOnlyForPod(pod.Spec.Containers, specPath.Child("containers"))...)
|
||||||
allErrs = append(allErrs, validateContainersOnlyForPod(pod.Spec.InitContainers, specPath.Child("initContainers"))...)
|
allErrs = append(allErrs, validateContainersOnlyForPod(pod.Spec.InitContainers, specPath.Child("initContainers"))...)
|
||||||
|
|
||||||
hugePageResources := sets.NewString()
|
if !opts.AllowMultipleHugePageResources {
|
||||||
for i := range pod.Spec.Containers {
|
allErrs = append(allErrs, ValidatePodSingleHugePageResources(pod, specPath)...)
|
||||||
resourceSet := toContainerResourcesSet(&pod.Spec.Containers[i])
|
|
||||||
for resourceStr := range resourceSet {
|
|
||||||
if v1helper.IsHugePageResourceName(v1.ResourceName(resourceStr)) {
|
|
||||||
hugePageResources.Insert(resourceStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(hugePageResources) > 1 {
|
|
||||||
allErrs = append(allErrs, field.Invalid(specPath, hugePageResources, "must use a single hugepage size in a pod spec"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
podIPsField := field.NewPath("status", "podIPs")
|
podIPsField := field.NewPath("status", "podIPs")
|
||||||
@ -3679,8 +3695,8 @@ func ValidateContainerUpdates(newContainers, oldContainers []core.Container, fld
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidatePodCreate validates a pod in the context of its initial create
|
// ValidatePodCreate validates a pod in the context of its initial create
|
||||||
func ValidatePodCreate(pod *core.Pod) field.ErrorList {
|
func ValidatePodCreate(pod *core.Pod, opts PodValidationOptions) field.ErrorList {
|
||||||
allErrs := ValidatePod(pod)
|
allErrs := ValidatePod(pod, opts)
|
||||||
|
|
||||||
fldPath := field.NewPath("spec")
|
fldPath := field.NewPath("spec")
|
||||||
// EphemeralContainers can only be set on update using the ephemeralcontainers subresource
|
// EphemeralContainers can only be set on update using the ephemeralcontainers subresource
|
||||||
@ -3693,12 +3709,16 @@ func ValidatePodCreate(pod *core.Pod) field.ErrorList {
|
|||||||
|
|
||||||
// ValidatePodUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields
|
// ValidatePodUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields
|
||||||
// that cannot be changed.
|
// that cannot be changed.
|
||||||
func ValidatePodUpdate(newPod, oldPod *core.Pod) field.ErrorList {
|
func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) field.ErrorList {
|
||||||
fldPath := field.NewPath("metadata")
|
fldPath := field.NewPath("metadata")
|
||||||
allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath)
|
allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath)
|
||||||
allErrs = append(allErrs, ValidatePodSpecificAnnotationUpdates(newPod, oldPod, fldPath.Child("annotations"))...)
|
allErrs = append(allErrs, ValidatePodSpecificAnnotationUpdates(newPod, oldPod, fldPath.Child("annotations"))...)
|
||||||
specPath := field.NewPath("spec")
|
specPath := field.NewPath("spec")
|
||||||
|
|
||||||
|
if !opts.AllowMultipleHugePageResources {
|
||||||
|
allErrs = append(allErrs, ValidatePodSingleHugePageResources(newPod, specPath)...)
|
||||||
|
}
|
||||||
|
|
||||||
// validate updateable fields:
|
// validate updateable fields:
|
||||||
// 1. spec.containers[*].image
|
// 1. spec.containers[*].image
|
||||||
// 2. spec.initContainers[*].image
|
// 2. spec.initContainers[*].image
|
||||||
|
@ -24,7 +24,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
@ -3908,114 +3908,186 @@ func TestValidateVolumes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHugePagesIsolation(t *testing.T) {
|
func TestHugePagesIsolation(t *testing.T) {
|
||||||
successCases := []core.Pod{
|
testCases := map[string]struct {
|
||||||
{ // Basic fields.
|
pod *core.Pod
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
|
enableHugePageStorageMediumSize bool
|
||||||
Spec: core.PodSpec{
|
expectError bool
|
||||||
Containers: []core.Container{
|
}{
|
||||||
{
|
"Valid: request hugepages-2Mi": {
|
||||||
Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
pod: &core.Pod{
|
||||||
Resources: core.ResourceRequirements{
|
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
|
||||||
Requests: core.ResourceList{
|
Spec: core.PodSpec{
|
||||||
core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
|
Containers: []core.Container{
|
||||||
core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
|
{
|
||||||
core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
|
Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||||
},
|
Resources: core.ResourceRequirements{
|
||||||
Limits: core.ResourceList{
|
Requests: core.ResourceList{
|
||||||
core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
|
core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
|
||||||
core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
|
core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
|
||||||
core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
|
core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
|
||||||
|
},
|
||||||
|
Limits: core.ResourceList{
|
||||||
|
core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
|
||||||
|
core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
|
||||||
|
core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
RestartPolicy: core.RestartPolicyAlways,
|
||||||
|
DNSPolicy: core.DNSClusterFirst,
|
||||||
},
|
},
|
||||||
RestartPolicy: core.RestartPolicyAlways,
|
|
||||||
DNSPolicy: core.DNSClusterFirst,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"Valid: HugePageStorageMediumSize enabled: request more than one hugepages size": {
|
||||||
|
pod: &core.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "hugepages-shared", Namespace: "ns"},
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
Containers: []core.Container{
|
||||||
|
{
|
||||||
|
Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||||
|
Resources: core.ResourceRequirements{
|
||||||
|
Requests: core.ResourceList{
|
||||||
|
core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
|
||||||
|
core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
|
||||||
|
core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
|
||||||
|
core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
|
||||||
|
},
|
||||||
|
Limits: core.ResourceList{
|
||||||
|
core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
|
||||||
|
core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
|
||||||
|
core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
|
||||||
|
core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RestartPolicy: core.RestartPolicyAlways,
|
||||||
|
DNSPolicy: core.DNSClusterFirst,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
enableHugePageStorageMediumSize: true,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
"Invalid: HugePageStorageMediumSize disabled: request hugepages-1Gi, limit hugepages-2Mi and hugepages-1Gi": {
|
||||||
|
pod: &core.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "hugepages-multiple", Namespace: "ns"},
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
Containers: []core.Container{
|
||||||
|
{
|
||||||
|
Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||||
|
Resources: core.ResourceRequirements{
|
||||||
|
Requests: core.ResourceList{
|
||||||
|
core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
|
||||||
|
core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
|
||||||
|
core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
|
||||||
|
},
|
||||||
|
Limits: core.ResourceList{
|
||||||
|
core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
|
||||||
|
core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
|
||||||
|
core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
|
||||||
|
core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RestartPolicy: core.RestartPolicyAlways,
|
||||||
|
DNSPolicy: core.DNSClusterFirst,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
"Invalid: not requesting cpu and memory": {
|
||||||
|
pod: &core.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "hugepages-requireCpuOrMemory", Namespace: "ns"},
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
Containers: []core.Container{
|
||||||
|
{
|
||||||
|
Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||||
|
Resources: core.ResourceRequirements{
|
||||||
|
Requests: core.ResourceList{
|
||||||
|
core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
|
||||||
|
},
|
||||||
|
Limits: core.ResourceList{
|
||||||
|
core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RestartPolicy: core.RestartPolicyAlways,
|
||||||
|
DNSPolicy: core.DNSClusterFirst,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
"Invalid: request 1Gi hugepages-2Mi but limit 2Gi": {
|
||||||
|
pod: &core.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "hugepages-shared", Namespace: "ns"},
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
Containers: []core.Container{
|
||||||
|
{
|
||||||
|
Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||||
|
Resources: core.ResourceRequirements{
|
||||||
|
Requests: core.ResourceList{
|
||||||
|
core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
|
||||||
|
core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
|
||||||
|
core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
|
||||||
|
},
|
||||||
|
Limits: core.ResourceList{
|
||||||
|
core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
|
||||||
|
core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
|
||||||
|
core.ResourceName("hugepages-2Mi"): resource.MustParse("2Gi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RestartPolicy: core.RestartPolicyAlways,
|
||||||
|
DNSPolicy: core.DNSClusterFirst,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
"Invalid: HugePageStorageMediumSize disabled: request more than one hugepages size": {
|
||||||
|
pod: &core.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "hugepages-shared", Namespace: "ns"},
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
Containers: []core.Container{
|
||||||
|
{
|
||||||
|
Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||||
|
Resources: core.ResourceRequirements{
|
||||||
|
Requests: core.ResourceList{
|
||||||
|
core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
|
||||||
|
core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
|
||||||
|
core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
|
||||||
|
core.ResourceName("hugepages-1Gi"): resource.MustParse("1Gi"),
|
||||||
|
},
|
||||||
|
Limits: core.ResourceList{
|
||||||
|
core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
|
||||||
|
core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
|
||||||
|
core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
|
||||||
|
core.ResourceName("hugepages-1Gi"): resource.MustParse("1Gi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RestartPolicy: core.RestartPolicyAlways,
|
||||||
|
DNSPolicy: core.DNSClusterFirst,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
failureCases := []core.Pod{
|
for tcName, tc := range testCases {
|
||||||
{ // Basic fields.
|
t.Run(tcName, func(t *testing.T) {
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "hugepages-requireCpuOrMemory", Namespace: "ns"},
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HugePageStorageMediumSize, tc.enableHugePageStorageMediumSize)()
|
||||||
Spec: core.PodSpec{
|
errs := ValidatePod(tc.pod, PodValidationOptions{tc.enableHugePageStorageMediumSize})
|
||||||
Containers: []core.Container{
|
if tc.expectError && len(errs) == 0 {
|
||||||
{
|
t.Errorf("Unexpected success")
|
||||||
Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
}
|
||||||
Resources: core.ResourceRequirements{
|
if !tc.expectError && len(errs) != 0 {
|
||||||
Requests: core.ResourceList{
|
t.Errorf("Unexpected error(s): %v", errs)
|
||||||
core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
|
}
|
||||||
},
|
})
|
||||||
Limits: core.ResourceList{
|
|
||||||
core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RestartPolicy: core.RestartPolicyAlways,
|
|
||||||
DNSPolicy: core.DNSClusterFirst,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // Basic fields.
|
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "hugepages-shared", Namespace: "ns"},
|
|
||||||
Spec: core.PodSpec{
|
|
||||||
Containers: []core.Container{
|
|
||||||
{
|
|
||||||
Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
|
||||||
Resources: core.ResourceRequirements{
|
|
||||||
Requests: core.ResourceList{
|
|
||||||
core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
|
|
||||||
core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
|
|
||||||
core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
|
|
||||||
},
|
|
||||||
Limits: core.ResourceList{
|
|
||||||
core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
|
|
||||||
core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
|
|
||||||
core.ResourceName("hugepages-2Mi"): resource.MustParse("2Gi"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RestartPolicy: core.RestartPolicyAlways,
|
|
||||||
DNSPolicy: core.DNSClusterFirst,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // Basic fields.
|
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "hugepages-multiple", Namespace: "ns"},
|
|
||||||
Spec: core.PodSpec{
|
|
||||||
Containers: []core.Container{
|
|
||||||
{
|
|
||||||
Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
|
||||||
Resources: core.ResourceRequirements{
|
|
||||||
Requests: core.ResourceList{
|
|
||||||
core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
|
|
||||||
core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
|
|
||||||
core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
|
|
||||||
},
|
|
||||||
Limits: core.ResourceList{
|
|
||||||
core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
|
|
||||||
core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
|
|
||||||
core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
|
|
||||||
core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RestartPolicy: core.RestartPolicyAlways,
|
|
||||||
DNSPolicy: core.DNSClusterFirst,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i := range successCases {
|
|
||||||
pod := &successCases[i]
|
|
||||||
if errs := ValidatePod(pod); len(errs) != 0 {
|
|
||||||
t.Errorf("Unexpected error for case[%d], err: %v", i, errs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := range failureCases {
|
|
||||||
pod := &failureCases[i]
|
|
||||||
if errs := ValidatePod(pod); len(errs) == 0 {
|
|
||||||
t.Errorf("Expected error for case[%d], pod: %v", i, pod.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7375,7 +7447,7 @@ func TestValidatePod(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, pod := range successCases {
|
for _, pod := range successCases {
|
||||||
if errs := ValidatePod(&pod); len(errs) != 0 {
|
if errs := ValidatePod(&pod, PodValidationOptions{}); len(errs) != 0 {
|
||||||
t.Errorf("expected success: %v", errs)
|
t.Errorf("expected success: %v", errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -8225,7 +8297,7 @@ func TestValidatePod(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
for k, v := range errorCases {
|
for k, v := range errorCases {
|
||||||
if errs := ValidatePod(&v.spec); len(errs) == 0 {
|
if errs := ValidatePod(&v.spec, PodValidationOptions{}); len(errs) == 0 {
|
||||||
t.Errorf("expected failure for %q", k)
|
t.Errorf("expected failure for %q", k)
|
||||||
} else if v.expectedError == "" {
|
} else if v.expectedError == "" {
|
||||||
t.Errorf("missing expectedError for %q, got %q", k, errs.ToAggregate().Error())
|
t.Errorf("missing expectedError for %q, got %q", k, errs.ToAggregate().Error())
|
||||||
@ -8965,7 +9037,7 @@ func TestValidatePodUpdate(t *testing.T) {
|
|||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
test.new.ObjectMeta.ResourceVersion = "1"
|
test.new.ObjectMeta.ResourceVersion = "1"
|
||||||
test.old.ObjectMeta.ResourceVersion = "1"
|
test.old.ObjectMeta.ResourceVersion = "1"
|
||||||
errs := ValidatePodUpdate(&test.new, &test.old)
|
errs := ValidatePodUpdate(&test.new, &test.old, PodValidationOptions{})
|
||||||
if test.err == "" {
|
if test.err == "" {
|
||||||
if len(errs) != 0 {
|
if len(errs) != 0 {
|
||||||
t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old)
|
t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old)
|
||||||
@ -14876,7 +14948,7 @@ func TestPodIPsValidation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
errs := ValidatePod(&testCase.pod)
|
errs := ValidatePod(&testCase.pod, PodValidationOptions{})
|
||||||
if len(errs) == 0 && testCase.expectError {
|
if len(errs) == 0 && testCase.expectError {
|
||||||
t.Errorf("expected failure for %s, but there were none", testCase.pod.Name)
|
t.Errorf("expected failure for %s, but there were none", testCase.pod.Name)
|
||||||
return
|
return
|
||||||
|
@ -540,6 +540,14 @@ const (
|
|||||||
//
|
//
|
||||||
// Enables a feature to make secrets and configmaps data immutable.
|
// Enables a feature to make secrets and configmaps data immutable.
|
||||||
ImmutableEphemeralVolumes featuregate.Feature = "ImmutableEphemeralVolumes"
|
ImmutableEphemeralVolumes featuregate.Feature = "ImmutableEphemeralVolumes"
|
||||||
|
|
||||||
|
// owner: @bart0sh
|
||||||
|
// alpha: v1.18
|
||||||
|
//
|
||||||
|
// Enables usage of HugePages-<size> in a volume medium,
|
||||||
|
// e.g. emptyDir:
|
||||||
|
// medium: HugePages-1Gi
|
||||||
|
HugePageStorageMediumSize featuregate.Feature = "HugePageStorageMediumSize"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -625,6 +633,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
|||||||
PodDisruptionBudget: {Default: true, PreRelease: featuregate.Beta},
|
PodDisruptionBudget: {Default: true, PreRelease: featuregate.Beta},
|
||||||
ServiceTopology: {Default: false, PreRelease: featuregate.Alpha},
|
ServiceTopology: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
ImmutableEphemeralVolumes: {Default: false, PreRelease: featuregate.Alpha},
|
ImmutableEphemeralVolumes: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
HugePageStorageMediumSize: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
|
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
|
||||||
// unintentionally on either side:
|
// unintentionally on either side:
|
||||||
|
@ -24,6 +24,7 @@ go_library(
|
|||||||
"//pkg/apis/core/install:go_default_library",
|
"//pkg/apis/core/install:go_default_library",
|
||||||
"//pkg/apis/core/v1:go_default_library",
|
"//pkg/apis/core/v1:go_default_library",
|
||||||
"//pkg/apis/core/validation:go_default_library",
|
"//pkg/apis/core/validation:go_default_library",
|
||||||
|
"//pkg/features:go_default_library",
|
||||||
"//pkg/kubelet/checkpoint:go_default_library",
|
"//pkg/kubelet/checkpoint:go_default_library",
|
||||||
"//pkg/kubelet/checkpointmanager:go_default_library",
|
"//pkg/kubelet/checkpointmanager:go_default_library",
|
||||||
"//pkg/kubelet/container:go_default_library",
|
"//pkg/kubelet/container:go_default_library",
|
||||||
@ -40,6 +41,7 @@ go_library(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
|
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/tools/record:go_default_library",
|
"//staging/src/k8s.io/client-go/tools/record:go_default_library",
|
||||||
|
@ -27,8 +27,10 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
|
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
|
|
||||||
// TODO: remove this import if
|
// TODO: remove this import if
|
||||||
// api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String() is changed
|
// api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String() is changed
|
||||||
@ -133,7 +135,10 @@ func tryDecodeSinglePod(data []byte, defaultFn defaultFunc) (parsed bool, pod *v
|
|||||||
if err = defaultFn(newPod); err != nil {
|
if err = defaultFn(newPod); err != nil {
|
||||||
return true, pod, err
|
return true, pod, err
|
||||||
}
|
}
|
||||||
if errs := validation.ValidatePod(newPod); len(errs) > 0 {
|
opts := validation.PodValidationOptions{
|
||||||
|
AllowMultipleHugePageResources: utilfeature.DefaultFeatureGate.Enabled(features.HugePageStorageMediumSize),
|
||||||
|
}
|
||||||
|
if errs := validation.ValidatePod(newPod, opts); len(errs) > 0 {
|
||||||
return true, pod, fmt.Errorf("invalid pod: %v", errs)
|
return true, pod, fmt.Errorf("invalid pod: %v", errs)
|
||||||
}
|
}
|
||||||
v1Pod := &v1.Pod{}
|
v1Pod := &v1.Pod{}
|
||||||
@ -157,13 +162,17 @@ func tryDecodePodList(data []byte, defaultFn defaultFunc) (parsed bool, pods v1.
|
|||||||
return false, pods, err
|
return false, pods, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
opts := validation.PodValidationOptions{
|
||||||
|
AllowMultipleHugePageResources: utilfeature.DefaultFeatureGate.Enabled(features.HugePageStorageMediumSize),
|
||||||
|
}
|
||||||
|
|
||||||
// Apply default values and validate pods.
|
// Apply default values and validate pods.
|
||||||
for i := range newPods.Items {
|
for i := range newPods.Items {
|
||||||
newPod := &newPods.Items[i]
|
newPod := &newPods.Items[i]
|
||||||
if err = defaultFn(newPod); err != nil {
|
if err = defaultFn(newPod); err != nil {
|
||||||
return true, pods, err
|
return true, pods, err
|
||||||
}
|
}
|
||||||
if errs := validation.ValidatePod(newPod); len(errs) > 0 {
|
if errs := validation.ValidatePod(newPod, opts); len(errs) > 0 {
|
||||||
err = fmt.Errorf("invalid pod: %v", errs)
|
err = fmt.Errorf("invalid pod: %v", errs)
|
||||||
return true, pods, err
|
return true, pods, err
|
||||||
}
|
}
|
||||||
|
@ -251,7 +251,7 @@ func TestStaticPodNameGenerate(t *testing.T) {
|
|||||||
if c.overwrite != "" {
|
if c.overwrite != "" {
|
||||||
pod.Name = c.overwrite
|
pod.Name = c.overwrite
|
||||||
}
|
}
|
||||||
errs := validation.ValidatePod(pod)
|
errs := validation.ValidatePod(pod, validation.PodValidationOptions{})
|
||||||
if c.shouldErr {
|
if c.shouldErr {
|
||||||
specNameErrored := false
|
specNameErrored := false
|
||||||
for _, err := range errs {
|
for _, err := range errs {
|
||||||
|
@ -90,7 +90,7 @@ func TestReadPodsFromFileExistAlready(t *testing.T) {
|
|||||||
if err := k8s_api_v1.Convert_v1_Pod_To_core_Pod(pod, internalPod, nil); err != nil {
|
if err := k8s_api_v1.Convert_v1_Pod_To_core_Pod(pod, internalPod, nil); err != nil {
|
||||||
t.Fatalf("%s: Cannot convert pod %#v, %#v", testCase.desc, pod, err)
|
t.Fatalf("%s: Cannot convert pod %#v, %#v", testCase.desc, pod, err)
|
||||||
}
|
}
|
||||||
if errs := validation.ValidatePod(internalPod); len(errs) > 0 {
|
if errs := validation.ValidatePod(internalPod, validation.PodValidationOptions{}); len(errs) > 0 {
|
||||||
t.Fatalf("%s: Invalid pod %#v, %#v", testCase.desc, internalPod, errs)
|
t.Fatalf("%s: Invalid pod %#v, %#v", testCase.desc, internalPod, errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -369,7 +369,7 @@ func expectUpdate(t *testing.T, ch chan interface{}, testCase *testCase) {
|
|||||||
if err := k8s_api_v1.Convert_v1_Pod_To_core_Pod(pod, internalPod, nil); err != nil {
|
if err := k8s_api_v1.Convert_v1_Pod_To_core_Pod(pod, internalPod, nil); err != nil {
|
||||||
t.Fatalf("%s: Cannot convert pod %#v, %#v", testCase.desc, pod, err)
|
t.Fatalf("%s: Cannot convert pod %#v, %#v", testCase.desc, pod, err)
|
||||||
}
|
}
|
||||||
if errs := validation.ValidatePod(internalPod); len(errs) > 0 {
|
if errs := validation.ValidatePod(internalPod, validation.PodValidationOptions{}); len(errs) > 0 {
|
||||||
t.Fatalf("%s: Invalid pod %#v, %#v", testCase.desc, internalPod, errs)
|
t.Fatalf("%s: Invalid pod %#v, %#v", testCase.desc, internalPod, errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -319,7 +319,7 @@ func TestExtractPodsFromHTTP(t *testing.T) {
|
|||||||
if err := k8s_api_v1.Convert_v1_Pod_To_core_Pod(pod, internalPod, nil); err != nil {
|
if err := k8s_api_v1.Convert_v1_Pod_To_core_Pod(pod, internalPod, nil); err != nil {
|
||||||
t.Fatalf("%s: Cannot convert pod %#v, %#v", testCase.desc, pod, err)
|
t.Fatalf("%s: Cannot convert pod %#v, %#v", testCase.desc, pod, err)
|
||||||
}
|
}
|
||||||
if errs := validation.ValidatePod(internalPod); len(errs) != 0 {
|
if errs := validation.ValidatePod(internalPod, validation.PodValidationOptions{}); len(errs) != 0 {
|
||||||
t.Errorf("%s: Expected no validation errors on %#v, Got %v", testCase.desc, pod, errs.ToAggregate())
|
t.Errorf("%s: Expected no validation errors on %#v, Got %v", testCase.desc, pod, errs.ToAggregate())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,11 @@ func (podStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object
|
|||||||
// Validate validates a new pod.
|
// Validate validates a new pod.
|
||||||
func (podStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
func (podStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||||
pod := obj.(*api.Pod)
|
pod := obj.(*api.Pod)
|
||||||
allErrs := validation.ValidatePodCreate(pod)
|
opts := validation.PodValidationOptions{
|
||||||
|
// Allow multiple huge pages on pod create if feature is enabled
|
||||||
|
AllowMultipleHugePageResources: utilfeature.DefaultFeatureGate.Enabled(features.HugePageStorageMediumSize),
|
||||||
|
}
|
||||||
|
allErrs := validation.ValidatePodCreate(pod, opts)
|
||||||
allErrs = append(allErrs, validation.ValidateConditionalPod(pod, nil, field.NewPath(""))...)
|
allErrs = append(allErrs, validation.ValidateConditionalPod(pod, nil, field.NewPath(""))...)
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
@ -104,8 +108,13 @@ func (podStrategy) AllowCreateOnUpdate() bool {
|
|||||||
|
|
||||||
// ValidateUpdate is the default update validation for an end user.
|
// ValidateUpdate is the default update validation for an end user.
|
||||||
func (podStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
func (podStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
errorList := validation.ValidatePod(obj.(*api.Pod))
|
oldFailsSingleHugepagesValidation := len(validation.ValidatePodSingleHugePageResources(old.(*api.Pod), field.NewPath("spec"))) > 0
|
||||||
errorList = append(errorList, validation.ValidatePodUpdate(obj.(*api.Pod), old.(*api.Pod))...)
|
opts := validation.PodValidationOptions{
|
||||||
|
// Allow multiple huge pages on pod create if feature is enabled or if the old pod already has multiple hugepages specified
|
||||||
|
AllowMultipleHugePageResources: oldFailsSingleHugepagesValidation || utilfeature.DefaultFeatureGate.Enabled(features.HugePageStorageMediumSize),
|
||||||
|
}
|
||||||
|
errorList := validation.ValidatePod(obj.(*api.Pod), opts)
|
||||||
|
errorList = append(errorList, validation.ValidatePodUpdate(obj.(*api.Pod), old.(*api.Pod), opts)...)
|
||||||
errorList = append(errorList, validation.ValidateConditionalPod(obj.(*api.Pod), old.(*api.Pod), field.NewPath(""))...)
|
errorList = append(errorList, validation.ValidateConditionalPod(obj.(*api.Pod), old.(*api.Pod), field.NewPath(""))...)
|
||||||
return errorList
|
return errorList
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ go_test(
|
|||||||
embed = [":go_default_library"],
|
embed = [":go_default_library"],
|
||||||
deps = select({
|
deps = select({
|
||||||
"@io_bazel_rules_go//go/platform:android": [
|
"@io_bazel_rules_go//go/platform:android": [
|
||||||
|
"//pkg/features:go_default_library",
|
||||||
"//pkg/volume:go_default_library",
|
"//pkg/volume:go_default_library",
|
||||||
"//pkg/volume/testing:go_default_library",
|
"//pkg/volume/testing:go_default_library",
|
||||||
"//pkg/volume/util:go_default_library",
|
"//pkg/volume/util:go_default_library",
|
||||||
@ -51,10 +52,13 @@ go_test(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
|
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
|
||||||
|
"//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
|
||||||
"//vendor/k8s.io/utils/mount:go_default_library",
|
"//vendor/k8s.io/utils/mount:go_default_library",
|
||||||
],
|
],
|
||||||
"@io_bazel_rules_go//go/platform:linux": [
|
"@io_bazel_rules_go//go/platform:linux": [
|
||||||
|
"//pkg/features:go_default_library",
|
||||||
"//pkg/volume:go_default_library",
|
"//pkg/volume:go_default_library",
|
||||||
"//pkg/volume/testing:go_default_library",
|
"//pkg/volume/testing:go_default_library",
|
||||||
"//pkg/volume/util:go_default_library",
|
"//pkg/volume/util:go_default_library",
|
||||||
@ -62,7 +66,9 @@ go_test(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
|
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
|
||||||
|
"//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
|
||||||
"//vendor/k8s.io/utils/mount:go_default_library",
|
"//vendor/k8s.io/utils/mount:go_default_library",
|
||||||
],
|
],
|
||||||
"//conditions:default": [],
|
"//conditions:default": [],
|
||||||
|
@ -25,7 +25,7 @@ import (
|
|||||||
"k8s.io/utils/mount"
|
"k8s.io/utils/mount"
|
||||||
utilstrings "k8s.io/utils/strings"
|
utilstrings "k8s.io/utils/strings"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
@ -160,7 +160,7 @@ type mountDetector interface {
|
|||||||
// returns (v1.StorageMediumMemory, false, nil), the caller knows that the path is
|
// returns (v1.StorageMediumMemory, false, nil), the caller knows that the path is
|
||||||
// on a memory FS (tmpfs on Linux) but is not the root mountpoint of
|
// on a memory FS (tmpfs on Linux) but is not the root mountpoint of
|
||||||
// that tmpfs.
|
// that tmpfs.
|
||||||
GetMountMedium(path string) (v1.StorageMedium, bool, error)
|
GetMountMedium(path string, requestedMedium v1.StorageMedium) (v1.StorageMedium, bool, *resource.Quantity, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmptyDir volumes are temporary directories exposed to the pod.
|
// EmptyDir volumes are temporary directories exposed to the pod.
|
||||||
@ -216,12 +216,12 @@ func (ed *emptyDir) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ed.medium {
|
switch {
|
||||||
case v1.StorageMediumDefault:
|
case ed.medium == v1.StorageMediumDefault:
|
||||||
err = ed.setupDir(dir)
|
err = ed.setupDir(dir)
|
||||||
case v1.StorageMediumMemory:
|
case ed.medium == v1.StorageMediumMemory:
|
||||||
err = ed.setupTmpfs(dir)
|
err = ed.setupTmpfs(dir)
|
||||||
case v1.StorageMediumHugePages:
|
case v1helper.IsHugePageMedium(ed.medium):
|
||||||
err = ed.setupHugepages(dir)
|
err = ed.setupHugepages(dir)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("unknown storage medium %q", ed.medium)
|
err = fmt.Errorf("unknown storage medium %q", ed.medium)
|
||||||
@ -261,7 +261,7 @@ func (ed *emptyDir) setupTmpfs(dir string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Make SetUp idempotent.
|
// Make SetUp idempotent.
|
||||||
medium, isMnt, err := ed.mountDetector.GetMountMedium(dir)
|
medium, isMnt, _, err := ed.mountDetector.GetMountMedium(dir, ed.medium)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -284,17 +284,34 @@ func (ed *emptyDir) setupHugepages(dir string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Make SetUp idempotent.
|
// Make SetUp idempotent.
|
||||||
medium, isMnt, err := ed.mountDetector.GetMountMedium(dir)
|
medium, isMnt, mountPageSize, err := ed.mountDetector.GetMountMedium(dir, ed.medium)
|
||||||
|
klog.V(3).Infof("pod %v: setupHugepages: medium: %s, isMnt: %v, dir: %s, err: %v", ed.pod.UID, medium, isMnt, dir, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// If the directory is a mountpoint with medium hugepages, there is no
|
// If the directory is a mountpoint with medium hugepages of the same page size,
|
||||||
// work to do since we are already in the desired state.
|
// there is no work to do since we are already in the desired state.
|
||||||
if isMnt && medium == v1.StorageMediumHugePages {
|
if isMnt && v1helper.IsHugePageMedium(medium) {
|
||||||
|
// Medium is: Hugepages
|
||||||
|
if ed.medium == v1.StorageMediumHugePages {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if mountPageSize == nil {
|
||||||
|
return fmt.Errorf("pod %v: mounted dir %s pagesize is not determined", ed.pod.UID, dir)
|
||||||
|
}
|
||||||
|
// Medium is: Hugepages-<size>
|
||||||
|
// Mounted page size and medium size must be equal
|
||||||
|
mediumSize, err := v1helper.HugePageSizeFromMedium(ed.medium)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if mountPageSize == nil || mediumSize.Cmp(*mountPageSize) != 0 {
|
||||||
|
return fmt.Errorf("pod %v: mounted dir %s pagesize '%s' and requested medium size '%s' differ", ed.pod.UID, dir, mountPageSize.String(), mediumSize.String())
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pageSizeMountOption, err := getPageSizeMountOptionFromPod(ed.pod)
|
pageSizeMountOption, err := getPageSizeMountOption(ed.medium, ed.pod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -303,33 +320,52 @@ func (ed *emptyDir) setupHugepages(dir string) error {
|
|||||||
return ed.mounter.Mount("nodev", dir, "hugetlbfs", []string{pageSizeMountOption})
|
return ed.mounter.Mount("nodev", dir, "hugetlbfs", []string{pageSizeMountOption})
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPageSizeMountOptionFromPod retrieves pageSize mount option from Pod's resources
|
// getPageSizeMountOption retrieves pageSize mount option from Pod's resources
|
||||||
// and validates pageSize options in all containers of given Pod.
|
// and medium and validates pageSize options in all containers of given Pod.
|
||||||
func getPageSizeMountOptionFromPod(pod *v1.Pod) (string, error) {
|
func getPageSizeMountOption(medium v1.StorageMedium, pod *v1.Pod) (string, error) {
|
||||||
pageSizeFound := false
|
pageSizeFound := false
|
||||||
pageSize := resource.Quantity{}
|
pageSize := resource.Quantity{}
|
||||||
// In some rare cases init containers can also consume Huge pages.
|
|
||||||
containers := append(pod.Spec.Containers, pod.Spec.InitContainers...)
|
var mediumPageSize resource.Quantity
|
||||||
for _, container := range containers {
|
if medium != v1.StorageMediumHugePages {
|
||||||
|
// medium is: Hugepages-<size>
|
||||||
|
var err error
|
||||||
|
mediumPageSize, err = v1helper.HugePageSizeFromMedium(medium)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In some rare cases init containers can also consume Huge pages
|
||||||
|
for _, container := range append(pod.Spec.Containers, pod.Spec.InitContainers...) {
|
||||||
// We can take request because limit and requests must match.
|
// We can take request because limit and requests must match.
|
||||||
for requestName := range container.Resources.Requests {
|
for requestName := range container.Resources.Requests {
|
||||||
if v1helper.IsHugePageResourceName(requestName) {
|
if !v1helper.IsHugePageResourceName(requestName) {
|
||||||
currentPageSize, err := v1helper.HugePageSizeFromResourceName(requestName)
|
continue
|
||||||
if err != nil {
|
}
|
||||||
return "", err
|
currentPageSize, err := v1helper.HugePageSizeFromResourceName(requestName)
|
||||||
}
|
if err != nil {
|
||||||
// PageSize for all volumes in a POD are equal, except for the first one discovered.
|
return "", err
|
||||||
|
}
|
||||||
|
if medium == v1.StorageMediumHugePages { // medium is: Hugepages, size is not specified
|
||||||
|
// PageSize for all volumes in a POD must be equal if medium is "Hugepages"
|
||||||
if pageSizeFound && pageSize.Cmp(currentPageSize) != 0 {
|
if pageSizeFound && pageSize.Cmp(currentPageSize) != 0 {
|
||||||
return "", fmt.Errorf("multiple pageSizes for huge pages in a single PodSpec")
|
return "", fmt.Errorf("medium: %s can't be used if container requests multiple huge page sizes", medium)
|
||||||
}
|
}
|
||||||
pageSize = currentPageSize
|
|
||||||
pageSizeFound = true
|
pageSizeFound = true
|
||||||
|
pageSize = currentPageSize
|
||||||
|
} else { // medium is: Hugepages-<size>
|
||||||
|
if currentPageSize.Cmp(mediumPageSize) == 0 {
|
||||||
|
pageSizeFound = true
|
||||||
|
pageSize = currentPageSize
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !pageSizeFound {
|
if !pageSizeFound {
|
||||||
return "", fmt.Errorf("hugePages storage requested, but there is no resource request for huge pages")
|
return "", fmt.Errorf("medium %s: hugePages storage requested, but there is no resource request for huge pages", medium)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%s=%s", hugePagesPageSizeMountOption, pageSize.String()), nil
|
return fmt.Sprintf("%s=%s", hugePagesPageSizeMountOption, pageSize.String()), nil
|
||||||
@ -393,7 +429,7 @@ func (ed *emptyDir) TearDownAt(dir string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Figure out the medium.
|
// Figure out the medium.
|
||||||
medium, isMnt, err := ed.mountDetector.GetMountMedium(dir)
|
medium, isMnt, _, err := ed.mountDetector.GetMountMedium(dir, ed.medium)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,14 @@ package emptydir
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
"k8s.io/utils/mount"
|
"k8s.io/utils/mount"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Defined by Linux - the type number for tmpfs mounts.
|
// Defined by Linux - the type number for tmpfs mounts.
|
||||||
@ -39,22 +41,71 @@ type realMountDetector struct {
|
|||||||
mounter mount.Interface
|
mounter mount.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *realMountDetector) GetMountMedium(path string) (v1.StorageMedium, bool, error) {
|
// getPageSize obtains page size from the 'pagesize' mount option of the
|
||||||
|
// mounted volume
|
||||||
|
func getPageSize(path string, mounter mount.Interface) (*resource.Quantity, error) {
|
||||||
|
// Get mount point data for the path
|
||||||
|
mountPoints, err := mounter.List()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error listing mount points: %v", err)
|
||||||
|
}
|
||||||
|
// Find mount point for the path
|
||||||
|
mountPoint, err := func(mps []mount.MountPoint, mpPath string) (*mount.MountPoint, error) {
|
||||||
|
for _, mp := range mps {
|
||||||
|
if mp.Path == mpPath {
|
||||||
|
return &mp, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("mount point for %s not found", mpPath)
|
||||||
|
}(mountPoints, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Get page size from the 'pagesize' option value
|
||||||
|
for _, opt := range mountPoint.Opts {
|
||||||
|
opt = strings.TrimSpace(opt)
|
||||||
|
prefix := "pagesize="
|
||||||
|
if strings.HasPrefix(opt, prefix) {
|
||||||
|
// NOTE: Adding suffix 'i' as result should be comparable with a medium size.
|
||||||
|
// pagesize mount option is specified without a suffix,
|
||||||
|
// e.g. pagesize=2M or pagesize=1024M for x86 CPUs
|
||||||
|
pageSize, err := resource.ParseQuantity(strings.TrimPrefix(opt, prefix) + "i")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting page size from '%s' mount option: %v", opt, err)
|
||||||
|
}
|
||||||
|
return &pageSize, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no pagesize option specified for %s mount", mountPoint.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *realMountDetector) GetMountMedium(path string, requestedMedium v1.StorageMedium) (v1.StorageMedium, bool, *resource.Quantity, error) {
|
||||||
klog.V(5).Infof("Determining mount medium of %v", path)
|
klog.V(5).Infof("Determining mount medium of %v", path)
|
||||||
notMnt, err := m.mounter.IsLikelyNotMountPoint(path)
|
notMnt, err := m.mounter.IsLikelyNotMountPoint(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return v1.StorageMediumDefault, false, fmt.Errorf("IsLikelyNotMountPoint(%q): %v", path, err)
|
return v1.StorageMediumDefault, false, nil, fmt.Errorf("IsLikelyNotMountPoint(%q): %v", path, err)
|
||||||
}
|
|
||||||
buf := unix.Statfs_t{}
|
|
||||||
if err := unix.Statfs(path, &buf); err != nil {
|
|
||||||
return v1.StorageMediumDefault, false, fmt.Errorf("statfs(%q): %v", path, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
klog.V(5).Infof("Statfs_t of %v: %+v", path, buf)
|
buf := unix.Statfs_t{}
|
||||||
if buf.Type == linuxTmpfsMagic {
|
if err := unix.Statfs(path, &buf); err != nil {
|
||||||
return v1.StorageMediumMemory, !notMnt, nil
|
return v1.StorageMediumDefault, false, nil, fmt.Errorf("statfs(%q): %v", path, err)
|
||||||
} else if int64(buf.Type) == linuxHugetlbfsMagic {
|
|
||||||
return v1.StorageMediumHugePages, !notMnt, nil
|
|
||||||
}
|
}
|
||||||
return v1.StorageMediumDefault, !notMnt, nil
|
|
||||||
|
klog.V(3).Infof("Statfs_t of %v: %+v", path, buf)
|
||||||
|
|
||||||
|
if buf.Type == linuxTmpfsMagic {
|
||||||
|
return v1.StorageMediumMemory, !notMnt, nil, nil
|
||||||
|
} else if int64(buf.Type) == linuxHugetlbfsMagic {
|
||||||
|
// Skip page size detection if requested medium doesn't have size specified
|
||||||
|
if requestedMedium == v1.StorageMediumHugePages {
|
||||||
|
return v1.StorageMediumHugePages, !notMnt, nil, nil
|
||||||
|
}
|
||||||
|
// Get page size for the volume mount
|
||||||
|
pageSize, err := getPageSize(path, m.mounter)
|
||||||
|
if err != nil {
|
||||||
|
return v1.StorageMediumHugePages, !notMnt, nil, err
|
||||||
|
}
|
||||||
|
return v1.StorageMediumHugePages, !notMnt, pageSize, nil
|
||||||
|
}
|
||||||
|
return v1.StorageMediumDefault, !notMnt, nil, nil
|
||||||
}
|
}
|
||||||
|
@ -19,20 +19,24 @@ limitations under the License.
|
|||||||
package emptydir
|
package emptydir
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/utils/mount"
|
"k8s.io/api/core/v1"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
utiltesting "k8s.io/client-go/util/testing"
|
utiltesting "k8s.io/client-go/util/testing"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/volume"
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
volumetest "k8s.io/kubernetes/pkg/volume/testing"
|
volumetest "k8s.io/kubernetes/pkg/volume/testing"
|
||||||
"k8s.io/kubernetes/pkg/volume/util"
|
"k8s.io/kubernetes/pkg/volume/util"
|
||||||
|
"k8s.io/utils/mount"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Construct an instance of a plugin, by name.
|
// Construct an instance of a plugin, by name.
|
||||||
@ -71,8 +75,8 @@ type fakeMountDetector struct {
|
|||||||
isMount bool
|
isMount bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fake *fakeMountDetector) GetMountMedium(path string) (v1.StorageMedium, bool, error) {
|
func (fake *fakeMountDetector) GetMountMedium(path string, requestedMedium v1.StorageMedium) (v1.StorageMedium, bool, *resource.Quantity, error) {
|
||||||
return fake.medium, fake.isMount, nil
|
return fake.medium, fake.isMount, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPluginEmptyRootContext(t *testing.T) {
|
func TestPluginEmptyRootContext(t *testing.T) {
|
||||||
@ -83,12 +87,33 @@ func TestPluginEmptyRootContext(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPluginHugetlbfs(t *testing.T) {
|
func TestPluginHugetlbfs(t *testing.T) {
|
||||||
doTestPlugin(t, pluginTestConfig{
|
testCases := map[string]struct {
|
||||||
medium: v1.StorageMediumHugePages,
|
medium v1.StorageMedium
|
||||||
expectedSetupMounts: 1,
|
enableHugePageStorageMediumSize bool
|
||||||
expectedTeardownMounts: 0,
|
}{
|
||||||
shouldBeMountedBeforeTeardown: true,
|
"HugePageStorageMediumSize enabled: medium without size": {
|
||||||
})
|
medium: "HugePages",
|
||||||
|
enableHugePageStorageMediumSize: true,
|
||||||
|
},
|
||||||
|
"HugePageStorageMediumSize disabled: medium without size": {
|
||||||
|
medium: "HugePages",
|
||||||
|
},
|
||||||
|
"HugePageStorageMediumSize enabled: medium with size": {
|
||||||
|
medium: "HugePages-2Mi",
|
||||||
|
enableHugePageStorageMediumSize: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for tcName, tc := range testCases {
|
||||||
|
t.Run(tcName, func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HugePageStorageMediumSize, tc.enableHugePageStorageMediumSize)()
|
||||||
|
doTestPlugin(t, pluginTestConfig{
|
||||||
|
medium: tc.medium,
|
||||||
|
expectedSetupMounts: 1,
|
||||||
|
expectedTeardownMounts: 0,
|
||||||
|
shouldBeMountedBeforeTeardown: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type pluginTestConfig struct {
|
type pluginTestConfig struct {
|
||||||
@ -307,11 +332,13 @@ func TestMetrics(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetHugePagesMountOptions(t *testing.T) {
|
func TestGetHugePagesMountOptions(t *testing.T) {
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
pod *v1.Pod
|
pod *v1.Pod
|
||||||
shouldFail bool
|
medium v1.StorageMedium
|
||||||
expectedResult string
|
shouldFail bool
|
||||||
|
expectedResult string
|
||||||
|
enableHugePageStorageMediumSize bool
|
||||||
}{
|
}{
|
||||||
"testWithProperValues": {
|
"ProperValues": {
|
||||||
pod: &v1.Pod{
|
pod: &v1.Pod{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
@ -325,10 +352,11 @@ func TestGetHugePagesMountOptions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
medium: v1.StorageMediumHugePages,
|
||||||
shouldFail: false,
|
shouldFail: false,
|
||||||
expectedResult: "pagesize=2Mi",
|
expectedResult: "pagesize=2Mi",
|
||||||
},
|
},
|
||||||
"testWithProperValuesAndDifferentPageSize": {
|
"ProperValuesAndDifferentPageSize": {
|
||||||
pod: &v1.Pod{
|
pod: &v1.Pod{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
@ -349,6 +377,7 @@ func TestGetHugePagesMountOptions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
medium: v1.StorageMediumHugePages,
|
||||||
shouldFail: false,
|
shouldFail: false,
|
||||||
expectedResult: "pagesize=1Gi",
|
expectedResult: "pagesize=1Gi",
|
||||||
},
|
},
|
||||||
@ -373,6 +402,7 @@ func TestGetHugePagesMountOptions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
medium: v1.StorageMediumHugePages,
|
||||||
shouldFail: false,
|
shouldFail: false,
|
||||||
expectedResult: "pagesize=1Gi",
|
expectedResult: "pagesize=1Gi",
|
||||||
},
|
},
|
||||||
@ -397,6 +427,7 @@ func TestGetHugePagesMountOptions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
medium: v1.StorageMediumHugePages,
|
||||||
shouldFail: true,
|
shouldFail: true,
|
||||||
expectedResult: "",
|
expectedResult: "",
|
||||||
},
|
},
|
||||||
@ -421,24 +452,463 @@ func TestGetHugePagesMountOptions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
shouldFail: true,
|
medium: v1.StorageMediumHugePages,
|
||||||
expectedResult: "",
|
shouldFail: true,
|
||||||
|
expectedResult: "",
|
||||||
|
enableHugePageStorageMediumSize: true,
|
||||||
},
|
},
|
||||||
"PodWithNoHugePagesRequest": {
|
"PodWithNoHugePagesRequest": {
|
||||||
pod: &v1.Pod{},
|
pod: &v1.Pod{},
|
||||||
|
medium: v1.StorageMediumHugePages,
|
||||||
|
shouldFail: true,
|
||||||
|
expectedResult: "",
|
||||||
|
},
|
||||||
|
"ProperValuesMultipleSizes": {
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Resources: v1.ResourceRequirements{
|
||||||
|
Requests: v1.ResourceList{
|
||||||
|
v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
|
||||||
|
v1.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
medium: v1.StorageMediumHugePagesPrefix + "1Gi",
|
||||||
|
shouldFail: false,
|
||||||
|
expectedResult: "pagesize=1Gi",
|
||||||
|
enableHugePageStorageMediumSize: true,
|
||||||
|
},
|
||||||
|
"InitContainerAndContainerHasProperValuesMultipleSizes": {
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
InitContainers: []v1.Container{
|
||||||
|
{
|
||||||
|
Resources: v1.ResourceRequirements{
|
||||||
|
Requests: v1.ResourceList{
|
||||||
|
v1.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
|
||||||
|
v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Resources: v1.ResourceRequirements{
|
||||||
|
Requests: v1.ResourceList{
|
||||||
|
v1.ResourceName("hugepages-1Gi"): resource.MustParse("4Gi"),
|
||||||
|
v1.ResourceName("hugepages-2Mi"): resource.MustParse("50Mi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
medium: v1.StorageMediumHugePagesPrefix + "2Mi",
|
||||||
|
shouldFail: false,
|
||||||
|
expectedResult: "pagesize=2Mi",
|
||||||
|
enableHugePageStorageMediumSize: true,
|
||||||
|
},
|
||||||
|
"MediumWithoutSizeMultipleSizes": {
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Resources: v1.ResourceRequirements{
|
||||||
|
Requests: v1.ResourceList{
|
||||||
|
v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
|
||||||
|
v1.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
medium: v1.StorageMediumHugePagesPrefix,
|
||||||
|
shouldFail: true,
|
||||||
|
expectedResult: "",
|
||||||
|
},
|
||||||
|
"IncorrectMediumFormatMultipleSizes": {
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Resources: v1.ResourceRequirements{
|
||||||
|
Requests: v1.ResourceList{
|
||||||
|
v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
|
||||||
|
v1.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
medium: "foo",
|
||||||
|
shouldFail: true,
|
||||||
|
expectedResult: "",
|
||||||
|
},
|
||||||
|
"MediumSizeDoesntMatchResourcesMultipleSizes": {
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Resources: v1.ResourceRequirements{
|
||||||
|
Requests: v1.ResourceList{
|
||||||
|
v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
|
||||||
|
v1.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
medium: v1.StorageMediumHugePagesPrefix + "1Mi",
|
||||||
shouldFail: true,
|
shouldFail: true,
|
||||||
expectedResult: "",
|
expectedResult: "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for testCaseName, testCase := range testCases {
|
for testCaseName, testCase := range testCases {
|
||||||
value, err := getPageSizeMountOptionFromPod(testCase.pod)
|
t.Run(testCaseName, func(t *testing.T) {
|
||||||
if testCase.shouldFail && err == nil {
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HugePageStorageMediumSize, testCase.enableHugePageStorageMediumSize)()
|
||||||
t.Errorf("Expected an error in %v", testCaseName)
|
value, err := getPageSizeMountOption(testCase.medium, testCase.pod)
|
||||||
} else if !testCase.shouldFail && err != nil {
|
if testCase.shouldFail && err == nil {
|
||||||
t.Errorf("Unexpected error in %v, got %v", testCaseName, err)
|
t.Errorf("%s: Unexpected success", testCaseName)
|
||||||
} else if testCase.expectedResult != value {
|
} else if !testCase.shouldFail && err != nil {
|
||||||
t.Errorf("Unexpected mountOptions for Pod. Expected %v, got %v", testCase.expectedResult, value)
|
t.Errorf("%s: Unexpected error: %v", testCaseName, err)
|
||||||
}
|
} else if testCase.expectedResult != value {
|
||||||
|
t.Errorf("%s: Unexpected mountOptions for Pod. Expected %v, got %v", testCaseName, testCase.expectedResult, value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testMountDetector struct {
|
||||||
|
pageSize *resource.Quantity
|
||||||
|
isMnt bool
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *testMountDetector) GetMountMedium(path string, requestedMedium v1.StorageMedium) (v1.StorageMedium, bool, *resource.Quantity, error) {
|
||||||
|
return v1.StorageMediumHugePages, md.isMnt, md.pageSize, md.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetupHugepages(t *testing.T) {
|
||||||
|
tmpdir, err := ioutil.TempDir("", "TestSetupHugepages")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
pageSize2Mi := resource.MustParse("2Mi")
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
path string
|
||||||
|
ed *emptyDir
|
||||||
|
shouldFail bool
|
||||||
|
}{
|
||||||
|
"Valid: mount expected": {
|
||||||
|
path: tmpdir,
|
||||||
|
ed: &emptyDir{
|
||||||
|
medium: v1.StorageMediumHugePages,
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Resources: v1.ResourceRequirements{
|
||||||
|
Requests: v1.ResourceList{
|
||||||
|
v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounter: &mount.FakeMounter{},
|
||||||
|
mountDetector: &testMountDetector{
|
||||||
|
pageSize: &resource.Quantity{},
|
||||||
|
isMnt: false,
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shouldFail: false,
|
||||||
|
},
|
||||||
|
"Valid: already mounted with correct pagesize": {
|
||||||
|
path: tmpdir,
|
||||||
|
ed: &emptyDir{
|
||||||
|
medium: "HugePages-2Mi",
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Resources: v1.ResourceRequirements{
|
||||||
|
Requests: v1.ResourceList{
|
||||||
|
v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounter: mount.NewFakeMounter([]mount.MountPoint{{Path: tmpdir, Opts: []string{"rw", "pagesize=2M", "realtime"}}}),
|
||||||
|
mountDetector: &testMountDetector{
|
||||||
|
pageSize: &pageSize2Mi,
|
||||||
|
isMnt: true,
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shouldFail: false,
|
||||||
|
},
|
||||||
|
"Valid: already mounted": {
|
||||||
|
path: tmpdir,
|
||||||
|
ed: &emptyDir{
|
||||||
|
medium: "HugePages",
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Resources: v1.ResourceRequirements{
|
||||||
|
Requests: v1.ResourceList{
|
||||||
|
v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounter: mount.NewFakeMounter([]mount.MountPoint{{Path: tmpdir, Opts: []string{"rw", "pagesize=2M", "realtime"}}}),
|
||||||
|
mountDetector: &testMountDetector{
|
||||||
|
pageSize: nil,
|
||||||
|
isMnt: true,
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shouldFail: false,
|
||||||
|
},
|
||||||
|
"Invalid: mounter is nil": {
|
||||||
|
path: tmpdir,
|
||||||
|
ed: &emptyDir{
|
||||||
|
medium: "HugePages-2Mi",
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Resources: v1.ResourceRequirements{
|
||||||
|
Requests: v1.ResourceList{
|
||||||
|
v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounter: nil,
|
||||||
|
},
|
||||||
|
shouldFail: true,
|
||||||
|
},
|
||||||
|
"Invalid: GetMountMedium error": {
|
||||||
|
path: tmpdir,
|
||||||
|
ed: &emptyDir{
|
||||||
|
medium: "HugePages-2Mi",
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Resources: v1.ResourceRequirements{
|
||||||
|
Requests: v1.ResourceList{
|
||||||
|
v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounter: mount.NewFakeMounter([]mount.MountPoint{{Path: tmpdir, Opts: []string{"rw", "pagesize=2M", "realtime"}}}),
|
||||||
|
mountDetector: &testMountDetector{
|
||||||
|
pageSize: &pageSize2Mi,
|
||||||
|
isMnt: true,
|
||||||
|
err: fmt.Errorf("GetMountMedium error"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shouldFail: true,
|
||||||
|
},
|
||||||
|
"Invalid: medium and page size differ": {
|
||||||
|
path: tmpdir,
|
||||||
|
ed: &emptyDir{
|
||||||
|
medium: "HugePages-1Gi",
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Resources: v1.ResourceRequirements{
|
||||||
|
Requests: v1.ResourceList{
|
||||||
|
v1.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounter: mount.NewFakeMounter([]mount.MountPoint{{Path: tmpdir, Opts: []string{"rw", "pagesize=2M", "realtime"}}}),
|
||||||
|
mountDetector: &testMountDetector{
|
||||||
|
pageSize: &pageSize2Mi,
|
||||||
|
isMnt: true,
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shouldFail: true,
|
||||||
|
},
|
||||||
|
"Invalid medium": {
|
||||||
|
path: tmpdir,
|
||||||
|
ed: &emptyDir{
|
||||||
|
medium: "HugePages-NN",
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Resources: v1.ResourceRequirements{
|
||||||
|
Requests: v1.ResourceList{
|
||||||
|
v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounter: &mount.FakeMounter{},
|
||||||
|
mountDetector: &testMountDetector{
|
||||||
|
pageSize: &resource.Quantity{},
|
||||||
|
isMnt: false,
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shouldFail: true,
|
||||||
|
},
|
||||||
|
"Invalid: setupDir fails": {
|
||||||
|
path: "",
|
||||||
|
ed: &emptyDir{
|
||||||
|
medium: v1.StorageMediumHugePages,
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Resources: v1.ResourceRequirements{
|
||||||
|
Requests: v1.ResourceList{
|
||||||
|
v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounter: &mount.FakeMounter{},
|
||||||
|
},
|
||||||
|
shouldFail: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for testCaseName, testCase := range testCases {
|
||||||
|
t.Run(testCaseName, func(t *testing.T) {
|
||||||
|
err := testCase.ed.setupHugepages(testCase.path)
|
||||||
|
if testCase.shouldFail && err == nil {
|
||||||
|
t.Errorf("%s: Unexpected success", testCaseName)
|
||||||
|
} else if !testCase.shouldFail && err != nil {
|
||||||
|
t.Errorf("%s: Unexpected error: %v", testCaseName, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPageSize(t *testing.T) {
|
||||||
|
mounter := &mount.FakeMounter{
|
||||||
|
MountPoints: []mount.MountPoint{
|
||||||
|
{
|
||||||
|
Device: "/dev/sda2",
|
||||||
|
Type: "ext4",
|
||||||
|
Path: "/",
|
||||||
|
Opts: []string{"rw", "relatime", "errors=remount-ro"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Device: "/dev/hugepages",
|
||||||
|
Type: "hugetlbfs",
|
||||||
|
Path: "/mnt/hugepages-2Mi",
|
||||||
|
Opts: []string{"rw", "relatime", "pagesize=2M"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Device: "sysfs",
|
||||||
|
Type: "sysfs",
|
||||||
|
Path: "/sys",
|
||||||
|
Opts: []string{"rw", "nosuid", "nodev", "noexec", "relatime"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Device: "/dev/hugepages",
|
||||||
|
Type: "hugetlbfs",
|
||||||
|
Path: "/mnt/hugepages-1Gi",
|
||||||
|
Opts: []string{"rw", "relatime", "pagesize=1024M"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Device: "/dev/hugepages",
|
||||||
|
Type: "hugetlbfs",
|
||||||
|
Path: "/mnt/noopt",
|
||||||
|
Opts: []string{"rw", "relatime"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Device: "/dev/hugepages",
|
||||||
|
Type: "hugetlbfs",
|
||||||
|
Path: "/mnt/badopt",
|
||||||
|
Opts: []string{"rw", "relatime", "pagesize=NN"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
path string
|
||||||
|
mounter mount.Interface
|
||||||
|
expectedResult resource.Quantity
|
||||||
|
shouldFail bool
|
||||||
|
}{
|
||||||
|
"Valid: existing 2Mi mount": {
|
||||||
|
path: "/mnt/hugepages-2Mi",
|
||||||
|
mounter: mounter,
|
||||||
|
shouldFail: false,
|
||||||
|
expectedResult: resource.MustParse("2Mi"),
|
||||||
|
},
|
||||||
|
"Valid: existing 1Gi mount": {
|
||||||
|
path: "/mnt/hugepages-1Gi",
|
||||||
|
mounter: mounter,
|
||||||
|
shouldFail: false,
|
||||||
|
expectedResult: resource.MustParse("1Gi"),
|
||||||
|
},
|
||||||
|
"Invalid: mount point doesn't exist": {
|
||||||
|
path: "/mnt/nomp",
|
||||||
|
mounter: mounter,
|
||||||
|
shouldFail: true,
|
||||||
|
},
|
||||||
|
"Invalid: no pagesize option": {
|
||||||
|
path: "/mnt/noopt",
|
||||||
|
mounter: mounter,
|
||||||
|
shouldFail: true,
|
||||||
|
},
|
||||||
|
"Invalid: incorrect pagesize option": {
|
||||||
|
path: "/mnt/badopt",
|
||||||
|
mounter: mounter,
|
||||||
|
shouldFail: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for testCaseName, testCase := range testCases {
|
||||||
|
t.Run(testCaseName, func(t *testing.T) {
|
||||||
|
pageSize, err := getPageSize(testCase.path, testCase.mounter)
|
||||||
|
if testCase.shouldFail && err == nil {
|
||||||
|
t.Errorf("%s: Unexpected success", testCaseName)
|
||||||
|
} else if !testCase.shouldFail && err != nil {
|
||||||
|
t.Errorf("%s: Unexpected error: %v", testCaseName, err)
|
||||||
|
}
|
||||||
|
if err == nil && pageSize.Cmp(testCase.expectedResult) != 0 {
|
||||||
|
t.Errorf("%s: Unexpected result: %s, expected: %s", testCaseName, pageSize.String(), testCase.expectedResult.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ limitations under the License.
|
|||||||
package emptydir
|
package emptydir
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
"k8s.io/utils/mount"
|
"k8s.io/utils/mount"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
@ -29,6 +30,6 @@ type realMountDetector struct {
|
|||||||
mounter mount.Interface
|
mounter mount.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *realMountDetector) GetMountMedium(path string) (v1.StorageMedium, bool, error) {
|
func (m *realMountDetector) GetMountMedium(path string, requestedMedium v1.StorageMedium) (v1.StorageMedium, bool, *resource.Quantity, error) {
|
||||||
return v1.StorageMediumDefault, false, nil
|
return v1.StorageMediumDefault, false, nil, nil
|
||||||
}
|
}
|
||||||
|
@ -887,9 +887,10 @@ type FlockerVolumeSource struct {
|
|||||||
type StorageMedium string
|
type StorageMedium string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
StorageMediumDefault StorageMedium = "" // use whatever the default is for the node, assume anything we don't explicitly handle is this
|
StorageMediumDefault StorageMedium = "" // use whatever the default is for the node, assume anything we don't explicitly handle is this
|
||||||
StorageMediumMemory StorageMedium = "Memory" // use memory (e.g. tmpfs on linux)
|
StorageMediumMemory StorageMedium = "Memory" // use memory (e.g. tmpfs on linux)
|
||||||
StorageMediumHugePages StorageMedium = "HugePages" // use hugepages
|
StorageMediumHugePages StorageMedium = "HugePages" // use hugepages
|
||||||
|
StorageMediumHugePagesPrefix StorageMedium = "HugePages-" // prefix for full medium notation HugePages-<size>
|
||||||
)
|
)
|
||||||
|
|
||||||
// Protocol defines network protocols supported for things like container ports.
|
// Protocol defines network protocols supported for things like container ports.
|
||||||
|
Loading…
Reference in New Issue
Block a user