quota_lsci_test.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. /*
  2. Copyright 2019 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package e2enode
  14. import (
  15. "fmt"
  16. "path/filepath"
  17. "time"
  18. "k8s.io/api/core/v1"
  19. "k8s.io/apimachinery/pkg/api/resource"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/kubernetes/pkg/features"
  22. kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
  23. "k8s.io/kubernetes/pkg/volume/util/fsquota"
  24. "k8s.io/kubernetes/test/e2e/framework"
  25. e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
  26. imageutils "k8s.io/kubernetes/test/utils/image"
  27. "k8s.io/utils/mount"
  28. "github.com/onsi/ginkgo"
  29. )
  30. const (
  31. LSCIQuotaFeature = features.LocalStorageCapacityIsolationFSQuotaMonitoring
  32. )
  33. func runOneQuotaTest(f *framework.Framework, quotasRequested bool) {
  34. evictionTestTimeout := 10 * time.Minute
  35. sizeLimit := resource.MustParse("100Mi")
  36. useOverLimit := 101 /* Mb */
  37. useUnderLimit := 99 /* Mb */
  38. // TODO: remove hardcoded kubelet volume directory path
  39. // framework.TestContext.KubeVolumeDir is currently not populated for node e2e
  40. // As for why we do this: see comment below at isXfs.
  41. if isXfs("/var/lib/kubelet") {
  42. useUnderLimit = 50 /* Mb */
  43. }
  44. priority := 0
  45. if quotasRequested {
  46. priority = 1
  47. }
  48. ginkgo.Context(fmt.Sprintf(testContextFmt, fmt.Sprintf("use quotas for LSCI monitoring (quotas enabled: %v)", quotasRequested)), func() {
  49. tempSetCurrentKubeletConfig(f, func(initialConfig *kubeletconfig.KubeletConfiguration) {
  50. defer withFeatureGate(LSCIQuotaFeature, quotasRequested)()
  51. // TODO: remove hardcoded kubelet volume directory path
  52. // framework.TestContext.KubeVolumeDir is currently not populated for node e2e
  53. if quotasRequested && !supportsQuotas("/var/lib/kubelet") {
  54. // No point in running this as a positive test if quotas are not
  55. // enabled on the underlying filesystem.
  56. e2eskipper.Skipf("Cannot run LocalStorageCapacityIsolationQuotaMonitoring on filesystem without project quota enabled")
  57. }
  58. // setting a threshold to 0% disables; non-empty map overrides default value (necessary due to omitempty)
  59. initialConfig.EvictionHard = map[string]string{"memory.available": "0%"}
  60. initialConfig.FeatureGates[string(LSCIQuotaFeature)] = quotasRequested
  61. })
  62. runEvictionTest(f, evictionTestTimeout, noPressure, noStarvedResource, logDiskMetrics, []podEvictSpec{
  63. {
  64. evictionPriority: priority, // This pod should be evicted because of emptyDir violation only if quotas are enabled
  65. pod: diskConcealingPod(fmt.Sprintf("emptydir-concealed-disk-over-sizelimit-quotas-%v", quotasRequested), useOverLimit, &v1.VolumeSource{
  66. EmptyDir: &v1.EmptyDirVolumeSource{SizeLimit: &sizeLimit},
  67. }, v1.ResourceRequirements{}),
  68. },
  69. {
  70. evictionPriority: 0, // This pod should not be evicted because it uses less than its limit (test for quotas)
  71. pod: diskConcealingPod(fmt.Sprintf("emptydir-concealed-disk-under-sizelimit-quotas-%v", quotasRequested), useUnderLimit, &v1.VolumeSource{
  72. EmptyDir: &v1.EmptyDirVolumeSource{SizeLimit: &sizeLimit},
  73. }, v1.ResourceRequirements{}),
  74. },
  75. })
  76. })
  77. }
  78. // LocalStorageCapacityIsolationQuotaMonitoring tests that quotas are
  79. // used for monitoring rather than du. The mechanism is to create a
  80. // pod that creates a file, deletes it, and writes data to it. If
  81. // quotas are used to monitor, it will detect this deleted-but-in-use
  82. // file; if du is used to monitor, it will not detect this.
  83. var _ = framework.KubeDescribe("LocalStorageCapacityIsolationQuotaMonitoring [Slow] [Serial] [Disruptive] [Feature:LocalStorageCapacityIsolationQuota][NodeFeature:LSCIQuotaMonitoring]", func() {
  84. f := framework.NewDefaultFramework("localstorage-quota-monitoring-test")
  85. runOneQuotaTest(f, true)
  86. runOneQuotaTest(f, false)
  87. })
  88. const (
  89. writeConcealedPodCommand = `
  90. my $file = "%s.bin";
  91. open OUT, ">$file" || die "Cannot open $file: $!\n";
  92. unlink "$file" || die "Cannot unlink $file: $!\n";
  93. my $a = "a";
  94. foreach (1..20) { $a = "$a$a"; }
  95. foreach (1..%d) { syswrite(OUT, $a); }
  96. sleep 999999;`
  97. )
  98. // This is needed for testing eviction of pods using disk space in concealed files; the shell has no convenient
  99. // way of performing I/O to a concealed file, and the busybox image doesn't contain Perl.
  100. func diskConcealingPod(name string, diskConsumedMB int, volumeSource *v1.VolumeSource, resources v1.ResourceRequirements) *v1.Pod {
  101. path := ""
  102. volumeMounts := []v1.VolumeMount{}
  103. volumes := []v1.Volume{}
  104. if volumeSource != nil {
  105. path = volumeMountPath
  106. volumeMounts = []v1.VolumeMount{{MountPath: volumeMountPath, Name: volumeName}}
  107. volumes = []v1.Volume{{Name: volumeName, VolumeSource: *volumeSource}}
  108. }
  109. return &v1.Pod{
  110. ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-pod", name)},
  111. Spec: v1.PodSpec{
  112. RestartPolicy: v1.RestartPolicyNever,
  113. Containers: []v1.Container{
  114. {
  115. Image: imageutils.GetE2EImage(imageutils.Perl),
  116. Name: fmt.Sprintf("%s-container", name),
  117. Command: []string{
  118. "perl",
  119. "-e",
  120. fmt.Sprintf(writeConcealedPodCommand, filepath.Join(path, "file"), diskConsumedMB),
  121. },
  122. Resources: resources,
  123. VolumeMounts: volumeMounts,
  124. },
  125. },
  126. Volumes: volumes,
  127. },
  128. }
  129. }
  130. // Don't bother returning an error; if something goes wrong,
  131. // simply treat it as "no".
  132. func supportsQuotas(dir string) bool {
  133. supportsQuota, err := fsquota.SupportsQuotas(mount.New(""), dir)
  134. return supportsQuota && err == nil
  135. }