node_container_manager_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. // +build linux
  2. /*
  3. Copyright 2017 The Kubernetes Authors.
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. */
  14. package e2enode
  15. import (
  16. "context"
  17. "fmt"
  18. "io/ioutil"
  19. "path/filepath"
  20. "strconv"
  21. "strings"
  22. "time"
  23. "k8s.io/api/core/v1"
  24. "k8s.io/apimachinery/pkg/api/resource"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
  27. "k8s.io/kubernetes/pkg/kubelet/cm"
  28. "k8s.io/kubernetes/pkg/kubelet/stats/pidlimit"
  29. "k8s.io/kubernetes/test/e2e/framework"
  30. "github.com/onsi/ginkgo"
  31. "github.com/onsi/gomega"
  32. )
  33. func setDesiredConfiguration(initialConfig *kubeletconfig.KubeletConfiguration) {
  34. initialConfig.EnforceNodeAllocatable = []string{"pods", kubeReservedCgroup, systemReservedCgroup}
  35. initialConfig.SystemReserved = map[string]string{
  36. string(v1.ResourceCPU): "100m",
  37. string(v1.ResourceMemory): "100Mi",
  38. string(pidlimit.PIDs): "1000",
  39. }
  40. initialConfig.KubeReserved = map[string]string{
  41. string(v1.ResourceCPU): "100m",
  42. string(v1.ResourceMemory): "100Mi",
  43. string(pidlimit.PIDs): "738",
  44. }
  45. initialConfig.EvictionHard = map[string]string{"memory.available": "100Mi"}
  46. // Necessary for allocatable cgroup creation.
  47. initialConfig.CgroupsPerQOS = true
  48. initialConfig.KubeReservedCgroup = kubeReservedCgroup
  49. initialConfig.SystemReservedCgroup = systemReservedCgroup
  50. }
  51. var _ = framework.KubeDescribe("Node Container Manager [Serial]", func() {
  52. f := framework.NewDefaultFramework("node-container-manager")
  53. ginkgo.Describe("Validate Node Allocatable [NodeFeature:NodeAllocatable]", func() {
  54. ginkgo.It("sets up the node and runs the test", func() {
  55. framework.ExpectNoError(runTest(f))
  56. })
  57. })
  58. })
  59. func expectFileValToEqual(filePath string, expectedValue, delta int64) error {
  60. out, err := ioutil.ReadFile(filePath)
  61. if err != nil {
  62. return fmt.Errorf("failed to read file %q", filePath)
  63. }
  64. actual, err := strconv.ParseInt(strings.TrimSpace(string(out)), 10, 64)
  65. if err != nil {
  66. return fmt.Errorf("failed to parse output %v", err)
  67. }
  68. // Ensure that values are within a delta range to work around rounding errors.
  69. if (actual < (expectedValue - delta)) || (actual > (expectedValue + delta)) {
  70. return fmt.Errorf("Expected value at %q to be between %d and %d. Got %d", filePath, (expectedValue - delta), (expectedValue + delta), actual)
  71. }
  72. return nil
  73. }
  74. func getAllocatableLimits(cpu, memory, pids string, capacity v1.ResourceList) (*resource.Quantity, *resource.Quantity, *resource.Quantity) {
  75. var allocatableCPU, allocatableMemory, allocatablePIDs *resource.Quantity
  76. // Total cpu reservation is 200m.
  77. for k, v := range capacity {
  78. if k == v1.ResourceCPU {
  79. c := v.DeepCopy()
  80. allocatableCPU = &c
  81. allocatableCPU.Sub(resource.MustParse(cpu))
  82. }
  83. if k == v1.ResourceMemory {
  84. c := v.DeepCopy()
  85. allocatableMemory = &c
  86. allocatableMemory.Sub(resource.MustParse(memory))
  87. }
  88. }
  89. // Process IDs are not a node allocatable, so we have to do this ad hoc
  90. pidlimits, err := pidlimit.Stats()
  91. if err == nil && pidlimits != nil && pidlimits.MaxPID != nil {
  92. allocatablePIDs = resource.NewQuantity(int64(*pidlimits.MaxPID), resource.DecimalSI)
  93. allocatablePIDs.Sub(resource.MustParse(pids))
  94. }
  95. return allocatableCPU, allocatableMemory, allocatablePIDs
  96. }
  97. const (
  98. kubeReservedCgroup = "kube-reserved"
  99. systemReservedCgroup = "system-reserved"
  100. )
  101. func createIfNotExists(cm cm.CgroupManager, cgroupConfig *cm.CgroupConfig) error {
  102. if !cm.Exists(cgroupConfig.Name) {
  103. if err := cm.Create(cgroupConfig); err != nil {
  104. return err
  105. }
  106. }
  107. return nil
  108. }
  109. func createTemporaryCgroupsForReservation(cgroupManager cm.CgroupManager) error {
  110. // Create kube reserved cgroup
  111. cgroupConfig := &cm.CgroupConfig{
  112. Name: cm.NewCgroupName(cm.RootCgroupName, kubeReservedCgroup),
  113. }
  114. if err := createIfNotExists(cgroupManager, cgroupConfig); err != nil {
  115. return err
  116. }
  117. // Create system reserved cgroup
  118. cgroupConfig.Name = cm.NewCgroupName(cm.RootCgroupName, systemReservedCgroup)
  119. return createIfNotExists(cgroupManager, cgroupConfig)
  120. }
  121. func destroyTemporaryCgroupsForReservation(cgroupManager cm.CgroupManager) error {
  122. // Create kube reserved cgroup
  123. cgroupConfig := &cm.CgroupConfig{
  124. Name: cm.NewCgroupName(cm.RootCgroupName, kubeReservedCgroup),
  125. }
  126. if err := cgroupManager.Destroy(cgroupConfig); err != nil {
  127. return err
  128. }
  129. cgroupConfig.Name = cm.NewCgroupName(cm.RootCgroupName, systemReservedCgroup)
  130. return cgroupManager.Destroy(cgroupConfig)
  131. }
  132. func runTest(f *framework.Framework) error {
  133. var oldCfg *kubeletconfig.KubeletConfiguration
  134. subsystems, err := cm.GetCgroupSubsystems()
  135. if err != nil {
  136. return err
  137. }
  138. // Get current kubelet configuration
  139. oldCfg, err = getCurrentKubeletConfig()
  140. if err != nil {
  141. return err
  142. }
  143. // Create a cgroup manager object for manipulating cgroups.
  144. cgroupManager := cm.NewCgroupManager(subsystems, oldCfg.CgroupDriver)
  145. defer destroyTemporaryCgroupsForReservation(cgroupManager)
  146. defer func() {
  147. if oldCfg != nil {
  148. framework.ExpectNoError(setKubeletConfiguration(f, oldCfg))
  149. }
  150. }()
  151. if err := createTemporaryCgroupsForReservation(cgroupManager); err != nil {
  152. return err
  153. }
  154. newCfg := oldCfg.DeepCopy()
  155. // Change existing kubelet configuration
  156. setDesiredConfiguration(newCfg)
  157. // Set the new kubelet configuration.
  158. err = setKubeletConfiguration(f, newCfg)
  159. if err != nil {
  160. return err
  161. }
  162. // Set new config and current config.
  163. currentConfig := newCfg
  164. expectedNAPodCgroup := cm.ParseCgroupfsToCgroupName(currentConfig.CgroupRoot)
  165. expectedNAPodCgroup = cm.NewCgroupName(expectedNAPodCgroup, "kubepods")
  166. if !cgroupManager.Exists(expectedNAPodCgroup) {
  167. return fmt.Errorf("Expected Node Allocatable Cgroup Does not exist")
  168. }
  169. // TODO: Update cgroupManager to expose a Status interface to get current Cgroup Settings.
  170. // The node may not have updated capacity and allocatable yet, so check that it happens eventually.
  171. gomega.Eventually(func() error {
  172. nodeList, err := f.ClientSet.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
  173. if err != nil {
  174. return err
  175. }
  176. if len(nodeList.Items) != 1 {
  177. return fmt.Errorf("Unexpected number of node objects for node e2e. Expects only one node: %+v", nodeList)
  178. }
  179. node := nodeList.Items[0]
  180. capacity := node.Status.Capacity
  181. allocatableCPU, allocatableMemory, allocatablePIDs := getAllocatableLimits("200m", "200Mi", "1738", capacity)
  182. // Total Memory reservation is 200Mi excluding eviction thresholds.
  183. // Expect CPU shares on node allocatable cgroup to equal allocatable.
  184. if err := expectFileValToEqual(filepath.Join(subsystems.MountPoints["cpu"], "kubepods", "cpu.shares"), int64(cm.MilliCPUToShares(allocatableCPU.MilliValue())), 10); err != nil {
  185. return err
  186. }
  187. // Expect Memory limit on node allocatable cgroup to equal allocatable.
  188. if err := expectFileValToEqual(filepath.Join(subsystems.MountPoints["memory"], "kubepods", "memory.limit_in_bytes"), allocatableMemory.Value(), 0); err != nil {
  189. return err
  190. }
  191. // Expect PID limit on node allocatable cgroup to equal allocatable.
  192. if err := expectFileValToEqual(filepath.Join(subsystems.MountPoints["pids"], "kubepods", "pids.max"), allocatablePIDs.Value(), 0); err != nil {
  193. return err
  194. }
  195. // Check that Allocatable reported to scheduler includes eviction thresholds.
  196. schedulerAllocatable := node.Status.Allocatable
  197. // Memory allocatable should take into account eviction thresholds.
  198. // Process IDs are not a scheduler resource and as such cannot be tested here.
  199. allocatableCPU, allocatableMemory, _ = getAllocatableLimits("200m", "300Mi", "1738", capacity)
  200. // Expect allocatable to include all resources in capacity.
  201. if len(schedulerAllocatable) != len(capacity) {
  202. return fmt.Errorf("Expected all resources in capacity to be found in allocatable")
  203. }
  204. // CPU based evictions are not supported.
  205. if allocatableCPU.Cmp(schedulerAllocatable[v1.ResourceCPU]) != 0 {
  206. return fmt.Errorf("Unexpected cpu allocatable value exposed by the node. Expected: %v, got: %v, capacity: %v", allocatableCPU, schedulerAllocatable[v1.ResourceCPU], capacity[v1.ResourceCPU])
  207. }
  208. if allocatableMemory.Cmp(schedulerAllocatable[v1.ResourceMemory]) != 0 {
  209. return fmt.Errorf("Unexpected memory allocatable value exposed by the node. Expected: %v, got: %v, capacity: %v", allocatableMemory, schedulerAllocatable[v1.ResourceMemory], capacity[v1.ResourceMemory])
  210. }
  211. return nil
  212. }, time.Minute, 5*time.Second).Should(gomega.BeNil())
  213. kubeReservedCgroupName := cm.NewCgroupName(cm.RootCgroupName, kubeReservedCgroup)
  214. if !cgroupManager.Exists(kubeReservedCgroupName) {
  215. return fmt.Errorf("Expected kube reserved cgroup Does not exist")
  216. }
  217. // Expect CPU shares on kube reserved cgroup to equal it's reservation which is `100m`.
  218. kubeReservedCPU := resource.MustParse(currentConfig.KubeReserved[string(v1.ResourceCPU)])
  219. if err := expectFileValToEqual(filepath.Join(subsystems.MountPoints["cpu"], cgroupManager.Name(kubeReservedCgroupName), "cpu.shares"), int64(cm.MilliCPUToShares(kubeReservedCPU.MilliValue())), 10); err != nil {
  220. return err
  221. }
  222. // Expect Memory limit kube reserved cgroup to equal configured value `100Mi`.
  223. kubeReservedMemory := resource.MustParse(currentConfig.KubeReserved[string(v1.ResourceMemory)])
  224. if err := expectFileValToEqual(filepath.Join(subsystems.MountPoints["memory"], cgroupManager.Name(kubeReservedCgroupName), "memory.limit_in_bytes"), kubeReservedMemory.Value(), 0); err != nil {
  225. return err
  226. }
  227. // Expect process ID limit kube reserved cgroup to equal configured value `738`.
  228. kubeReservedPIDs := resource.MustParse(currentConfig.KubeReserved[string(pidlimit.PIDs)])
  229. if err := expectFileValToEqual(filepath.Join(subsystems.MountPoints["pids"], cgroupManager.Name(kubeReservedCgroupName), "pids.max"), kubeReservedPIDs.Value(), 0); err != nil {
  230. return err
  231. }
  232. systemReservedCgroupName := cm.NewCgroupName(cm.RootCgroupName, systemReservedCgroup)
  233. if !cgroupManager.Exists(systemReservedCgroupName) {
  234. return fmt.Errorf("Expected system reserved cgroup Does not exist")
  235. }
  236. // Expect CPU shares on system reserved cgroup to equal it's reservation which is `100m`.
  237. systemReservedCPU := resource.MustParse(currentConfig.SystemReserved[string(v1.ResourceCPU)])
  238. if err := expectFileValToEqual(filepath.Join(subsystems.MountPoints["cpu"], cgroupManager.Name(systemReservedCgroupName), "cpu.shares"), int64(cm.MilliCPUToShares(systemReservedCPU.MilliValue())), 10); err != nil {
  239. return err
  240. }
  241. // Expect Memory limit on node allocatable cgroup to equal allocatable.
  242. systemReservedMemory := resource.MustParse(currentConfig.SystemReserved[string(v1.ResourceMemory)])
  243. if err := expectFileValToEqual(filepath.Join(subsystems.MountPoints["memory"], cgroupManager.Name(systemReservedCgroupName), "memory.limit_in_bytes"), systemReservedMemory.Value(), 0); err != nil {
  244. return err
  245. }
  246. // Expect process ID limit system reserved cgroup to equal configured value `1000`.
  247. systemReservedPIDs := resource.MustParse(currentConfig.SystemReserved[string(pidlimit.PIDs)])
  248. if err := expectFileValToEqual(filepath.Join(subsystems.MountPoints["pids"], cgroupManager.Name(systemReservedCgroupName), "pids.max"), systemReservedPIDs.Value(), 0); err != nil {
  249. return err
  250. }
  251. return nil
  252. }