cluster_autoscaler_scalability.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. /*
  2. Copyright 2016 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 autoscaling
  14. import (
  15. "encoding/json"
  16. "fmt"
  17. "math"
  18. "strings"
  19. "time"
  20. "k8s.io/api/core/v1"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apimachinery/pkg/fields"
  23. "k8s.io/apimachinery/pkg/types"
  24. "k8s.io/apimachinery/pkg/util/strategicpatch"
  25. clientset "k8s.io/client-go/kubernetes"
  26. "k8s.io/kubernetes/test/e2e/framework"
  27. testutils "k8s.io/kubernetes/test/utils"
  28. imageutils "k8s.io/kubernetes/test/utils/image"
  29. "github.com/onsi/ginkgo"
  30. "github.com/onsi/gomega"
  31. "k8s.io/klog"
  32. )
  33. const (
  34. memoryReservationTimeout = 5 * time.Minute
  35. largeResizeTimeout = 8 * time.Minute
  36. largeScaleUpTimeout = 10 * time.Minute
  37. maxNodes = 1000
  38. )
  39. type clusterPredicates struct {
  40. nodes int
  41. }
  42. type scaleUpTestConfig struct {
  43. initialNodes int
  44. initialPods int
  45. extraPods *testutils.RCConfig
  46. expectedResult *clusterPredicates
  47. }
  48. var _ = framework.KubeDescribe("Cluster size autoscaler scalability [Slow]", func() {
  49. f := framework.NewDefaultFramework("autoscaling")
  50. var c clientset.Interface
  51. var nodeCount int
  52. var coresPerNode int
  53. var memCapacityMb int
  54. var originalSizes map[string]int
  55. var sum int
  56. ginkgo.BeforeEach(func() {
  57. framework.SkipUnlessProviderIs("gce", "gke", "kubemark")
  58. // Check if Cloud Autoscaler is enabled by trying to get its ConfigMap.
  59. _, err := f.ClientSet.CoreV1().ConfigMaps("kube-system").Get("cluster-autoscaler-status", metav1.GetOptions{})
  60. if err != nil {
  61. framework.Skipf("test expects Cluster Autoscaler to be enabled")
  62. }
  63. c = f.ClientSet
  64. if originalSizes == nil {
  65. originalSizes = make(map[string]int)
  66. sum = 0
  67. for _, mig := range strings.Split(framework.TestContext.CloudConfig.NodeInstanceGroup, ",") {
  68. size, err := framework.GroupSize(mig)
  69. framework.ExpectNoError(err)
  70. ginkgo.By(fmt.Sprintf("Initial size of %s: %d", mig, size))
  71. originalSizes[mig] = size
  72. sum += size
  73. }
  74. }
  75. framework.ExpectNoError(framework.WaitForReadyNodes(c, sum, scaleUpTimeout))
  76. nodes := framework.GetReadySchedulableNodesOrDie(f.ClientSet)
  77. nodeCount = len(nodes.Items)
  78. gomega.Expect(nodeCount).NotTo(gomega.BeZero())
  79. cpu := nodes.Items[0].Status.Capacity[v1.ResourceCPU]
  80. mem := nodes.Items[0].Status.Capacity[v1.ResourceMemory]
  81. coresPerNode = int((&cpu).MilliValue() / 1000)
  82. memCapacityMb = int((&mem).Value() / 1024 / 1024)
  83. gomega.Expect(nodeCount).Should(gomega.Equal(sum))
  84. if framework.ProviderIs("gke") {
  85. val, err := isAutoscalerEnabled(3)
  86. framework.ExpectNoError(err)
  87. if !val {
  88. err = enableAutoscaler("default-pool", 3, 5)
  89. framework.ExpectNoError(err)
  90. }
  91. }
  92. })
  93. ginkgo.AfterEach(func() {
  94. ginkgo.By(fmt.Sprintf("Restoring initial size of the cluster"))
  95. setMigSizes(originalSizes)
  96. framework.ExpectNoError(framework.WaitForReadyNodes(c, nodeCount, scaleDownTimeout))
  97. nodes, err := c.CoreV1().Nodes().List(metav1.ListOptions{})
  98. framework.ExpectNoError(err)
  99. s := time.Now()
  100. makeSchedulableLoop:
  101. for start := time.Now(); time.Since(start) < makeSchedulableTimeout; time.Sleep(makeSchedulableDelay) {
  102. for _, n := range nodes.Items {
  103. err = makeNodeSchedulable(c, &n, true)
  104. switch err.(type) {
  105. case CriticalAddonsOnlyError:
  106. continue makeSchedulableLoop
  107. default:
  108. framework.ExpectNoError(err)
  109. }
  110. }
  111. break
  112. }
  113. klog.Infof("Made nodes schedulable again in %v", time.Since(s).String())
  114. })
  115. ginkgo.It("should scale up at all [Feature:ClusterAutoscalerScalability1]", func() {
  116. perNodeReservation := int(float64(memCapacityMb) * 0.95)
  117. replicasPerNode := 10
  118. additionalNodes := maxNodes - nodeCount
  119. replicas := additionalNodes * replicasPerNode
  120. additionalReservation := additionalNodes * perNodeReservation
  121. // saturate cluster
  122. reservationCleanup := ReserveMemory(f, "some-pod", nodeCount*2, nodeCount*perNodeReservation, true, memoryReservationTimeout)
  123. defer reservationCleanup()
  124. framework.ExpectNoError(waitForAllCaPodsReadyInNamespace(f, c))
  125. // configure pending pods & expected scale up
  126. rcConfig := reserveMemoryRCConfig(f, "extra-pod-1", replicas, additionalReservation, largeScaleUpTimeout)
  127. expectedResult := createClusterPredicates(nodeCount + additionalNodes)
  128. config := createScaleUpTestConfig(nodeCount, nodeCount, rcConfig, expectedResult)
  129. // run test
  130. testCleanup := simpleScaleUpTest(f, config)
  131. defer testCleanup()
  132. })
  133. ginkgo.It("should scale up twice [Feature:ClusterAutoscalerScalability2]", func() {
  134. perNodeReservation := int(float64(memCapacityMb) * 0.95)
  135. replicasPerNode := 10
  136. additionalNodes1 := int(math.Ceil(0.7 * maxNodes))
  137. additionalNodes2 := int(math.Ceil(0.25 * maxNodes))
  138. if additionalNodes1+additionalNodes2 > maxNodes {
  139. additionalNodes2 = maxNodes - additionalNodes1
  140. }
  141. replicas1 := additionalNodes1 * replicasPerNode
  142. replicas2 := additionalNodes2 * replicasPerNode
  143. klog.Infof("cores per node: %v", coresPerNode)
  144. // saturate cluster
  145. initialReplicas := nodeCount
  146. reservationCleanup := ReserveMemory(f, "some-pod", initialReplicas, nodeCount*perNodeReservation, true, memoryReservationTimeout)
  147. defer reservationCleanup()
  148. framework.ExpectNoError(waitForAllCaPodsReadyInNamespace(f, c))
  149. klog.Infof("Reserved successfully")
  150. // configure pending pods & expected scale up #1
  151. rcConfig := reserveMemoryRCConfig(f, "extra-pod-1", replicas1, additionalNodes1*perNodeReservation, largeScaleUpTimeout)
  152. expectedResult := createClusterPredicates(nodeCount + additionalNodes1)
  153. config := createScaleUpTestConfig(nodeCount, nodeCount, rcConfig, expectedResult)
  154. // run test #1
  155. tolerateUnreadyNodes := additionalNodes1 / 20
  156. tolerateUnreadyPods := (initialReplicas + replicas1) / 20
  157. testCleanup1 := simpleScaleUpTestWithTolerance(f, config, tolerateUnreadyNodes, tolerateUnreadyPods)
  158. defer testCleanup1()
  159. klog.Infof("Scaled up once")
  160. // configure pending pods & expected scale up #2
  161. rcConfig2 := reserveMemoryRCConfig(f, "extra-pod-2", replicas2, additionalNodes2*perNodeReservation, largeScaleUpTimeout)
  162. expectedResult2 := createClusterPredicates(nodeCount + additionalNodes1 + additionalNodes2)
  163. config2 := createScaleUpTestConfig(nodeCount+additionalNodes1, nodeCount+additionalNodes2, rcConfig2, expectedResult2)
  164. // run test #2
  165. tolerateUnreadyNodes = maxNodes / 20
  166. tolerateUnreadyPods = (initialReplicas + replicas1 + replicas2) / 20
  167. testCleanup2 := simpleScaleUpTestWithTolerance(f, config2, tolerateUnreadyNodes, tolerateUnreadyPods)
  168. defer testCleanup2()
  169. klog.Infof("Scaled up twice")
  170. })
  171. ginkgo.It("should scale down empty nodes [Feature:ClusterAutoscalerScalability3]", func() {
  172. perNodeReservation := int(float64(memCapacityMb) * 0.7)
  173. replicas := int(math.Ceil(maxNodes * 0.7))
  174. totalNodes := maxNodes
  175. // resize cluster to totalNodes
  176. newSizes := map[string]int{
  177. anyKey(originalSizes): totalNodes,
  178. }
  179. setMigSizes(newSizes)
  180. framework.ExpectNoError(framework.WaitForReadyNodes(f.ClientSet, totalNodes, largeResizeTimeout))
  181. // run replicas
  182. rcConfig := reserveMemoryRCConfig(f, "some-pod", replicas, replicas*perNodeReservation, largeScaleUpTimeout)
  183. expectedResult := createClusterPredicates(totalNodes)
  184. config := createScaleUpTestConfig(totalNodes, totalNodes, rcConfig, expectedResult)
  185. tolerateUnreadyNodes := totalNodes / 10
  186. tolerateUnreadyPods := replicas / 10
  187. testCleanup := simpleScaleUpTestWithTolerance(f, config, tolerateUnreadyNodes, tolerateUnreadyPods)
  188. defer testCleanup()
  189. // check if empty nodes are scaled down
  190. framework.ExpectNoError(WaitForClusterSizeFunc(f.ClientSet,
  191. func(size int) bool {
  192. return size <= replicas+3 // leaving space for non-evictable kube-system pods
  193. }, scaleDownTimeout))
  194. })
  195. ginkgo.It("should scale down underutilized nodes [Feature:ClusterAutoscalerScalability4]", func() {
  196. perPodReservation := int(float64(memCapacityMb) * 0.01)
  197. // underutilizedNodes are 10% full
  198. underutilizedPerNodeReplicas := 10
  199. // fullNodes are 70% full
  200. fullPerNodeReplicas := 70
  201. totalNodes := maxNodes
  202. underutilizedRatio := 0.3
  203. maxDelta := 30
  204. // resize cluster to totalNodes
  205. newSizes := map[string]int{
  206. anyKey(originalSizes): totalNodes,
  207. }
  208. setMigSizes(newSizes)
  209. framework.ExpectNoError(framework.WaitForReadyNodes(f.ClientSet, totalNodes, largeResizeTimeout))
  210. // annotate all nodes with no-scale-down
  211. ScaleDownDisabledKey := "cluster-autoscaler.kubernetes.io/scale-down-disabled"
  212. nodes, err := f.ClientSet.CoreV1().Nodes().List(metav1.ListOptions{
  213. FieldSelector: fields.Set{
  214. "spec.unschedulable": "false",
  215. }.AsSelector().String(),
  216. })
  217. framework.ExpectNoError(err)
  218. framework.ExpectNoError(addAnnotation(f, nodes.Items, ScaleDownDisabledKey, "true"))
  219. // distribute pods using replication controllers taking up space that should
  220. // be empty after pods are distributed
  221. underutilizedNodesNum := int(float64(maxNodes) * underutilizedRatio)
  222. fullNodesNum := totalNodes - underutilizedNodesNum
  223. podDistribution := []podBatch{
  224. {numNodes: fullNodesNum, podsPerNode: fullPerNodeReplicas},
  225. {numNodes: underutilizedNodesNum, podsPerNode: underutilizedPerNodeReplicas}}
  226. cleanup := distributeLoad(f, f.Namespace.Name, "10-70", podDistribution, perPodReservation,
  227. int(0.95*float64(memCapacityMb)), map[string]string{}, largeScaleUpTimeout)
  228. defer cleanup()
  229. // enable scale down again
  230. framework.ExpectNoError(addAnnotation(f, nodes.Items, ScaleDownDisabledKey, "false"))
  231. // wait for scale down to start. Node deletion takes a long time, so we just
  232. // wait for maximum of 30 nodes deleted
  233. nodesToScaleDownCount := int(float64(totalNodes) * 0.1)
  234. if nodesToScaleDownCount > maxDelta {
  235. nodesToScaleDownCount = maxDelta
  236. }
  237. expectedSize := totalNodes - nodesToScaleDownCount
  238. timeout := time.Duration(nodesToScaleDownCount)*time.Minute + scaleDownTimeout
  239. framework.ExpectNoError(WaitForClusterSizeFunc(f.ClientSet, func(size int) bool {
  240. return size <= expectedSize
  241. }, timeout))
  242. })
  243. ginkgo.It("shouldn't scale down with underutilized nodes due to host port conflicts [Feature:ClusterAutoscalerScalability5]", func() {
  244. fullReservation := int(float64(memCapacityMb) * 0.9)
  245. hostPortPodReservation := int(float64(memCapacityMb) * 0.3)
  246. totalNodes := maxNodes
  247. reservedPort := 4321
  248. // resize cluster to totalNodes
  249. newSizes := map[string]int{
  250. anyKey(originalSizes): totalNodes,
  251. }
  252. setMigSizes(newSizes)
  253. framework.ExpectNoError(framework.WaitForReadyNodes(f.ClientSet, totalNodes, largeResizeTimeout))
  254. divider := int(float64(totalNodes) * 0.7)
  255. fullNodesCount := divider
  256. underutilizedNodesCount := totalNodes - fullNodesCount
  257. ginkgo.By("Reserving full nodes")
  258. // run RC1 w/o host port
  259. cleanup := ReserveMemory(f, "filling-pod", fullNodesCount, fullNodesCount*fullReservation, true, largeScaleUpTimeout*2)
  260. defer cleanup()
  261. ginkgo.By("Reserving host ports on remaining nodes")
  262. // run RC2 w/ host port
  263. cleanup2 := createHostPortPodsWithMemory(f, "underutilizing-host-port-pod", underutilizedNodesCount, reservedPort, underutilizedNodesCount*hostPortPodReservation, largeScaleUpTimeout)
  264. defer cleanup2()
  265. waitForAllCaPodsReadyInNamespace(f, c)
  266. // wait and check scale down doesn't occur
  267. ginkgo.By(fmt.Sprintf("Sleeping %v minutes...", scaleDownTimeout.Minutes()))
  268. time.Sleep(scaleDownTimeout)
  269. ginkgo.By("Checking if the number of nodes is as expected")
  270. nodes := framework.GetReadySchedulableNodesOrDie(f.ClientSet)
  271. klog.Infof("Nodes: %v, expected: %v", len(nodes.Items), totalNodes)
  272. gomega.Expect(len(nodes.Items)).Should(gomega.Equal(totalNodes))
  273. })
  274. ginkgo.Specify("CA ignores unschedulable pods while scheduling schedulable pods [Feature:ClusterAutoscalerScalability6]", func() {
  275. // Start a number of pods saturating existing nodes.
  276. perNodeReservation := int(float64(memCapacityMb) * 0.80)
  277. replicasPerNode := 10
  278. initialPodReplicas := nodeCount * replicasPerNode
  279. initialPodsTotalMemory := nodeCount * perNodeReservation
  280. reservationCleanup := ReserveMemory(f, "initial-pod", initialPodReplicas, initialPodsTotalMemory, true /* wait for pods to run */, memoryReservationTimeout)
  281. defer reservationCleanup()
  282. framework.ExpectNoError(waitForAllCaPodsReadyInNamespace(f, c))
  283. // Configure a number of unschedulable pods.
  284. unschedulableMemReservation := memCapacityMb * 2
  285. unschedulablePodReplicas := 1000
  286. totalMemReservation := unschedulableMemReservation * unschedulablePodReplicas
  287. timeToWait := 5 * time.Minute
  288. podsConfig := reserveMemoryRCConfig(f, "unschedulable-pod", unschedulablePodReplicas, totalMemReservation, timeToWait)
  289. framework.RunRC(*podsConfig) // Ignore error (it will occur because pods are unschedulable)
  290. defer framework.DeleteRCAndWaitForGC(f.ClientSet, f.Namespace.Name, podsConfig.Name)
  291. // Ensure that no new nodes have been added so far.
  292. gomega.Expect(framework.NumberOfReadyNodes(f.ClientSet)).To(gomega.Equal(nodeCount))
  293. // Start a number of schedulable pods to ensure CA reacts.
  294. additionalNodes := maxNodes - nodeCount
  295. replicas := additionalNodes * replicasPerNode
  296. totalMemory := additionalNodes * perNodeReservation
  297. rcConfig := reserveMemoryRCConfig(f, "extra-pod", replicas, totalMemory, largeScaleUpTimeout)
  298. expectedResult := createClusterPredicates(nodeCount + additionalNodes)
  299. config := createScaleUpTestConfig(nodeCount, initialPodReplicas, rcConfig, expectedResult)
  300. // Test that scale up happens, allowing 1000 unschedulable pods not to be scheduled.
  301. testCleanup := simpleScaleUpTestWithTolerance(f, config, 0, unschedulablePodReplicas)
  302. defer testCleanup()
  303. })
  304. })
  305. func anyKey(input map[string]int) string {
  306. for k := range input {
  307. return k
  308. }
  309. return ""
  310. }
  311. func simpleScaleUpTestWithTolerance(f *framework.Framework, config *scaleUpTestConfig, tolerateMissingNodeCount int, tolerateMissingPodCount int) func() error {
  312. // resize cluster to start size
  313. // run rc based on config
  314. ginkgo.By(fmt.Sprintf("Running RC %v from config", config.extraPods.Name))
  315. start := time.Now()
  316. framework.ExpectNoError(framework.RunRC(*config.extraPods))
  317. // check results
  318. if tolerateMissingNodeCount > 0 {
  319. // Tolerate some number of nodes not to be created.
  320. minExpectedNodeCount := config.expectedResult.nodes - tolerateMissingNodeCount
  321. framework.ExpectNoError(WaitForClusterSizeFunc(f.ClientSet,
  322. func(size int) bool { return size >= minExpectedNodeCount }, scaleUpTimeout))
  323. } else {
  324. framework.ExpectNoError(framework.WaitForReadyNodes(f.ClientSet, config.expectedResult.nodes, scaleUpTimeout))
  325. }
  326. klog.Infof("cluster is increased")
  327. if tolerateMissingPodCount > 0 {
  328. framework.ExpectNoError(waitForCaPodsReadyInNamespace(f, f.ClientSet, tolerateMissingPodCount))
  329. } else {
  330. framework.ExpectNoError(waitForAllCaPodsReadyInNamespace(f, f.ClientSet))
  331. }
  332. timeTrack(start, fmt.Sprintf("Scale up to %v", config.expectedResult.nodes))
  333. return func() error {
  334. return framework.DeleteRCAndWaitForGC(f.ClientSet, f.Namespace.Name, config.extraPods.Name)
  335. }
  336. }
  337. func simpleScaleUpTest(f *framework.Framework, config *scaleUpTestConfig) func() error {
  338. return simpleScaleUpTestWithTolerance(f, config, 0, 0)
  339. }
  340. func reserveMemoryRCConfig(f *framework.Framework, id string, replicas, megabytes int, timeout time.Duration) *testutils.RCConfig {
  341. return &testutils.RCConfig{
  342. Client: f.ClientSet,
  343. Name: id,
  344. Namespace: f.Namespace.Name,
  345. Timeout: timeout,
  346. Image: imageutils.GetPauseImageName(),
  347. Replicas: replicas,
  348. MemRequest: int64(1024 * 1024 * megabytes / replicas),
  349. }
  350. }
  351. func createScaleUpTestConfig(nodes, pods int, extraPods *testutils.RCConfig, expectedResult *clusterPredicates) *scaleUpTestConfig {
  352. return &scaleUpTestConfig{
  353. initialNodes: nodes,
  354. initialPods: pods,
  355. extraPods: extraPods,
  356. expectedResult: expectedResult,
  357. }
  358. }
  359. func createClusterPredicates(nodes int) *clusterPredicates {
  360. return &clusterPredicates{
  361. nodes: nodes,
  362. }
  363. }
  364. func addAnnotation(f *framework.Framework, nodes []v1.Node, key, value string) error {
  365. for _, node := range nodes {
  366. oldData, err := json.Marshal(node)
  367. if err != nil {
  368. return err
  369. }
  370. if node.Annotations == nil {
  371. node.Annotations = make(map[string]string)
  372. }
  373. node.Annotations[key] = value
  374. newData, err := json.Marshal(node)
  375. if err != nil {
  376. return err
  377. }
  378. patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Node{})
  379. if err != nil {
  380. return err
  381. }
  382. _, err = f.ClientSet.CoreV1().Nodes().Patch(string(node.Name), types.StrategicMergePatchType, patchBytes)
  383. if err != nil {
  384. return err
  385. }
  386. }
  387. return nil
  388. }
  389. func createHostPortPodsWithMemory(f *framework.Framework, id string, replicas, port, megabytes int, timeout time.Duration) func() error {
  390. ginkgo.By(fmt.Sprintf("Running RC which reserves host port and memory"))
  391. request := int64(1024 * 1024 * megabytes / replicas)
  392. config := &testutils.RCConfig{
  393. Client: f.ClientSet,
  394. Name: id,
  395. Namespace: f.Namespace.Name,
  396. Timeout: timeout,
  397. Image: imageutils.GetPauseImageName(),
  398. Replicas: replicas,
  399. HostPorts: map[string]int{"port1": port},
  400. MemRequest: request,
  401. }
  402. err := framework.RunRC(*config)
  403. framework.ExpectNoError(err)
  404. return func() error {
  405. return framework.DeleteRCAndWaitForGC(f.ClientSet, f.Namespace.Name, id)
  406. }
  407. }
  408. type podBatch struct {
  409. numNodes int
  410. podsPerNode int
  411. }
  412. // distributeLoad distributes the pods in the way described by podDostribution,
  413. // assuming all pods will have the same memory reservation and all nodes the same
  414. // memory capacity. This allows us generate the load on the cluster in the exact
  415. // way that we want.
  416. //
  417. // To achieve this we do the following:
  418. // 1. Create replication controllers that eat up all the space that should be
  419. // empty after setup, making sure they end up on different nodes by specifying
  420. // conflicting host port
  421. // 2. Create targer RC that will generate the load on the cluster
  422. // 3. Remove the rcs created in 1.
  423. func distributeLoad(f *framework.Framework, namespace string, id string, podDistribution []podBatch,
  424. podMemRequestMegabytes int, nodeMemCapacity int, labels map[string]string, timeout time.Duration) func() error {
  425. port := 8013
  426. // Create load-distribution RCs with one pod per node, reserving all remaining
  427. // memory to force the distribution of pods for the target RCs.
  428. // The load-distribution RCs will be deleted on function return.
  429. totalPods := 0
  430. for i, podBatch := range podDistribution {
  431. totalPods += podBatch.numNodes * podBatch.podsPerNode
  432. remainingMem := nodeMemCapacity - podBatch.podsPerNode*podMemRequestMegabytes
  433. replicas := podBatch.numNodes
  434. cleanup := createHostPortPodsWithMemory(f, fmt.Sprintf("load-distribution%d", i), replicas, port, remainingMem*replicas, timeout)
  435. defer cleanup()
  436. }
  437. framework.ExpectNoError(waitForAllCaPodsReadyInNamespace(f, f.ClientSet))
  438. // Create the target RC
  439. rcConfig := reserveMemoryRCConfig(f, id, totalPods, totalPods*podMemRequestMegabytes, timeout)
  440. framework.ExpectNoError(framework.RunRC(*rcConfig))
  441. framework.ExpectNoError(waitForAllCaPodsReadyInNamespace(f, f.ClientSet))
  442. return func() error {
  443. return framework.DeleteRCAndWaitForGC(f.ClientSet, f.Namespace.Name, id)
  444. }
  445. }
  446. func timeTrack(start time.Time, name string) {
  447. elapsed := time.Since(start)
  448. klog.Infof("%s took %s", name, elapsed)
  449. }