taint_based_evictions.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /*
  2. Copyright 2018 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 scheduling
  14. import (
  15. "errors"
  16. "fmt"
  17. "time"
  18. "k8s.io/api/core/v1"
  19. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  20. "k8s.io/apimachinery/pkg/fields"
  21. clientset "k8s.io/client-go/kubernetes"
  22. schedulerapi "k8s.io/kubernetes/pkg/scheduler/api"
  23. "k8s.io/kubernetes/test/e2e/framework"
  24. "github.com/onsi/ginkgo"
  25. )
  26. func newUnreachableNoExecuteTaint() *v1.Taint {
  27. return &v1.Taint{
  28. Key: schedulerapi.TaintNodeUnreachable,
  29. Effect: v1.TaintEffectNoExecute,
  30. }
  31. }
  32. func getTolerationSeconds(tolerations []v1.Toleration) (int64, error) {
  33. for _, t := range tolerations {
  34. if t.Key == schedulerapi.TaintNodeUnreachable && t.Effect == v1.TaintEffectNoExecute && t.Operator == v1.TolerationOpExists {
  35. return *t.TolerationSeconds, nil
  36. }
  37. }
  38. return 0, errors.New("cannot find toleration")
  39. }
  40. var _ = SIGDescribe("TaintBasedEvictions [Serial]", func() {
  41. f := framework.NewDefaultFramework("sched-taint-based-evictions")
  42. var cs clientset.Interface
  43. var ns string
  44. ginkgo.BeforeEach(func() {
  45. cs = f.ClientSet
  46. ns = f.Namespace.Name
  47. // skip if TaintBasedEvictions is not enabled
  48. // TODO(Huang-Wei): remove this when TaintBasedEvictions is GAed
  49. framework.SkipUnlessTaintBasedEvictionsEnabled()
  50. // it's required to run on a cluster that has more than 1 node
  51. // otherwise node lifecycle manager enters a fully disruption mode
  52. framework.SkipUnlessNodeCountIsAtLeast(2)
  53. })
  54. // This test verifies that when a node becomes unreachable
  55. // 1. node lifecycle manager generate a status change: [NodeReady=true, status=ConditionUnknown]
  56. // 1. it's applied with node.kubernetes.io/unreachable=:NoExecute taint
  57. // 2. pods without toleration are applied with toleration with tolerationSeconds=300
  58. // 3. pods with toleration and without tolerationSeconds won't be modified, and won't be evicted
  59. // 4. pods with toleration and with tolerationSeconds won't be modified, and will be evicted after tolerationSeconds
  60. // When network issue recovers, it's expected to see:
  61. // 5. node lifecycle manager generate a status change: [NodeReady=true, status=ConditionTrue]
  62. // 6. node.kubernetes.io/unreachable=:NoExecute taint is taken off the node
  63. ginkgo.It("Checks that the node becomes unreachable", func() {
  64. // find an available node
  65. nodeName := GetNodeThatCanRunPod(f)
  66. ginkgo.By("Finding an available node " + nodeName)
  67. // pod0 is a pod with unschedulable=:NoExecute toleration, and tolerationSeconds=0s
  68. // pod1 is a pod with unschedulable=:NoExecute toleration, and tolerationSeconds=200s
  69. // pod2 is a pod without any toleration
  70. base := "taint-based-eviction"
  71. tolerationSeconds := []int64{0, 200}
  72. numPods := len(tolerationSeconds) + 1
  73. ginkgo.By(fmt.Sprintf("Preparing %v pods", numPods))
  74. pods := make([]*v1.Pod, numPods)
  75. zero := int64(0)
  76. // build pod0, pod1
  77. for i := 0; i < numPods-1; i++ {
  78. pods[i] = createPausePod(f, pausePodConfig{
  79. Name: fmt.Sprintf("%v-%v", base, i),
  80. NodeName: nodeName,
  81. Tolerations: []v1.Toleration{
  82. {
  83. Key: schedulerapi.TaintNodeUnreachable,
  84. Operator: v1.TolerationOpExists,
  85. Effect: v1.TaintEffectNoExecute,
  86. TolerationSeconds: &tolerationSeconds[i],
  87. },
  88. },
  89. DeletionGracePeriodSeconds: &zero,
  90. })
  91. }
  92. // build pod2
  93. pods[numPods-1] = createPausePod(f, pausePodConfig{
  94. Name: fmt.Sprintf("%v-%v", base, numPods-1),
  95. NodeName: nodeName,
  96. })
  97. ginkgo.By("Verifying all pods are running properly")
  98. for _, pod := range pods {
  99. framework.ExpectNoError(framework.WaitForPodRunningInNamespace(cs, pod))
  100. }
  101. // get the node API object
  102. nodeSelector := fields.OneTermEqualSelector("metadata.name", nodeName)
  103. nodeList, err := cs.CoreV1().Nodes().List(metav1.ListOptions{FieldSelector: nodeSelector.String()})
  104. if err != nil || len(nodeList.Items) != 1 {
  105. framework.Failf("expected no err, got %v; expected len(nodes) = 1, got %v", err, len(nodeList.Items))
  106. }
  107. node := nodeList.Items[0]
  108. ginkgo.By(fmt.Sprintf("Blocking traffic from node %s to the master", nodeName))
  109. host, err := framework.GetNodeExternalIP(&node)
  110. // TODO(Huang-Wei): make this case work for local provider
  111. // if err != nil {
  112. // host, err = framework.GetNodeInternalIP(&node)
  113. // }
  114. framework.ExpectNoError(err)
  115. masterAddresses := framework.GetAllMasterAddresses(cs)
  116. taint := newUnreachableNoExecuteTaint()
  117. defer func() {
  118. ginkgo.By(fmt.Sprintf("Unblocking traffic from node %s to the master", node.Name))
  119. for _, masterAddress := range masterAddresses {
  120. framework.UnblockNetwork(host, masterAddress)
  121. }
  122. if ginkgo.CurrentGinkgoTestDescription().Failed {
  123. framework.Failf("Current e2e test has failed, so return from here.")
  124. return
  125. }
  126. ginkgo.By(fmt.Sprintf("Expecting to see node %q becomes Ready", nodeName))
  127. framework.WaitForNodeToBeReady(cs, nodeName, time.Minute*1)
  128. ginkgo.By("Expecting to see unreachable=:NoExecute taint is taken off")
  129. err := framework.WaitForNodeHasTaintOrNot(cs, nodeName, taint, false, time.Second*30)
  130. framework.ExpectNoError(err)
  131. }()
  132. for _, masterAddress := range masterAddresses {
  133. framework.BlockNetwork(host, masterAddress)
  134. }
  135. ginkgo.By(fmt.Sprintf("Expecting to see node %q becomes NotReady", nodeName))
  136. if !framework.WaitForNodeToBeNotReady(cs, nodeName, time.Minute*3) {
  137. framework.Failf("node %q doesn't turn to NotReady after 3 minutes", nodeName)
  138. }
  139. ginkgo.By("Expecting to see unreachable=:NoExecute taint is applied")
  140. err = framework.WaitForNodeHasTaintOrNot(cs, nodeName, taint, true, time.Second*30)
  141. framework.ExpectNoError(err)
  142. ginkgo.By("Expecting pod0 to be evicted immediately")
  143. err = framework.WaitForPodCondition(cs, ns, pods[0].Name, "pod0 terminating", time.Second*15, func(pod *v1.Pod) (bool, error) {
  144. // as node is unreachable, pod0 is expected to be in Terminating status
  145. // rather than getting deleted
  146. if pod.DeletionTimestamp != nil {
  147. return true, nil
  148. }
  149. return false, nil
  150. })
  151. framework.ExpectNoError(err)
  152. ginkgo.By("Expecting pod2 to be updated with a toleration with tolerationSeconds=300")
  153. err = framework.WaitForPodCondition(cs, ns, pods[2].Name, "pod2 updated with tolerationSeconds=300", time.Second*15, func(pod *v1.Pod) (bool, error) {
  154. if seconds, err := getTolerationSeconds(pod.Spec.Tolerations); err == nil {
  155. return seconds == 300, nil
  156. }
  157. return false, nil
  158. })
  159. framework.ExpectNoError(err)
  160. ginkgo.By("Expecting pod1 to be unchanged")
  161. livePod1, err := cs.CoreV1().Pods(pods[1].Namespace).Get(pods[1].Name, metav1.GetOptions{})
  162. framework.ExpectNoError(err)
  163. seconds, err := getTolerationSeconds(livePod1.Spec.Tolerations)
  164. framework.ExpectNoError(err)
  165. if seconds != 200 {
  166. framework.Failf("expect tolerationSeconds of pod1 is 200, but got %v", seconds)
  167. }
  168. })
  169. })