quota_lsci_test.go 5.7 KB

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