istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/controllers/untaint/nodeuntainter_test.go (about) 1 // Copyright Istio Authors 2 // 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 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package untaint 16 17 import ( 18 "testing" 19 "time" 20 21 corev1 "k8s.io/api/core/v1" 22 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 24 "istio.io/istio/pilot/pkg/features" 25 kubelib "istio.io/istio/pkg/kube" 26 "istio.io/istio/pkg/kube/kclient/clienttest" 27 istiolog "istio.io/istio/pkg/log" 28 "istio.io/istio/pkg/test" 29 "istio.io/istio/pkg/test/util/assert" 30 "istio.io/istio/pkg/test/util/retry" 31 ) 32 33 const systemNS = "istio-system" 34 35 var cniPodLabels = map[string]string{ 36 "k8s-app": "istio-cni-node", 37 "some-other": "label", 38 } 39 40 type nodeTainterTestServer struct { 41 client kubelib.Client 42 pc clienttest.TestClient[*corev1.Pod] 43 nc clienttest.TestClient[*corev1.Node] 44 t *testing.T 45 } 46 47 func setupLogging() { 48 opts := istiolog.DefaultOptions() 49 opts.SetDefaultOutputLevel(istiolog.OverrideScopeName, istiolog.DebugLevel) 50 istiolog.Configure(opts) 51 for _, scope := range istiolog.Scopes() { 52 scope.SetOutputLevel(istiolog.DebugLevel) 53 } 54 } 55 56 func newNodeUntainterTestServer(t *testing.T) *nodeTainterTestServer { 57 stop := make(chan struct{}) 58 t.Cleanup(func() { close(stop) }) 59 client := kubelib.NewFakeClient() 60 61 nodeUntainter := NewNodeUntainter(stop, client, systemNS, systemNS) 62 go nodeUntainter.Run(stop) 63 client.RunAndWait(stop) 64 kubelib.WaitForCacheSync("test", stop, nodeUntainter.HasSynced) 65 66 pc := clienttest.Wrap(t, nodeUntainter.podsClient) 67 nc := clienttest.Wrap(t, nodeUntainter.nodesClient) 68 69 return &nodeTainterTestServer{ 70 client: client, 71 t: t, 72 pc: pc, 73 nc: nc, 74 } 75 } 76 77 func TestNodeUntainter(t *testing.T) { 78 setupLogging() 79 test.SetForTest(t, &features.EnableNodeUntaintControllers, true) 80 s := newNodeUntainterTestServer(t) 81 s.addTaintedNodes(t, "node1", "node2", "node3") 82 s.addPod(t, "node3", true, map[string]string{"k8s-app": "other-app"}, "") 83 s.addCniPod(t, "node2", false) 84 s.addCniPod(t, "node1", true) 85 s.assertNodeUntainted(t, "node1") 86 s.assertNodeTainted(t, "node2") 87 s.assertNodeTainted(t, "node3") 88 } 89 90 func TestNodeUntainterOnlyUntaintsWhenIstiocniInourNs(t *testing.T) { 91 test.SetForTest(t, &features.EnableNodeUntaintControllers, true) 92 s := newNodeUntainterTestServer(t) 93 s.addTaintedNodes(t, "node1", "node2") 94 s.addPod(t, "node2", true, cniPodLabels, "default") 95 s.addCniPod(t, "node1", true) 96 97 // wait for the untainter to run 98 s.assertNodeUntainted(t, "node1") 99 s.assertNodeTainted(t, "node2") 100 } 101 102 func (s *nodeTainterTestServer) assertNodeTainted(t *testing.T, node string) { 103 t.Helper() 104 assert.Equal(t, s.isNodeUntainted(node), false) 105 } 106 107 func (s *nodeTainterTestServer) assertNodeUntainted(t *testing.T, node string) { 108 t.Helper() 109 assert.EventuallyEqual(t, func() bool { 110 return s.isNodeUntainted(node) 111 }, true, retry.Timeout(time.Second*3)) 112 } 113 114 func (s *nodeTainterTestServer) isNodeUntainted(node string) bool { 115 n := s.nc.Get(node, "") 116 if n == nil { 117 return false 118 } 119 for _, t := range n.Spec.Taints { 120 if t.Key == TaintName { 121 return false 122 } 123 } 124 return true 125 } 126 127 func (s *nodeTainterTestServer) addTaintedNodes(t *testing.T, nodes ...string) { 128 t.Helper() 129 for _, node := range nodes { 130 node := generateNode(node, nil) 131 // add our special taint 132 node.Spec.Taints = append(node.Spec.Taints, corev1.Taint{ 133 Key: TaintName, 134 Value: "true", 135 Effect: corev1.TaintEffectNoSchedule, 136 }) 137 s.nc.Create(node) 138 } 139 } 140 141 func (s *nodeTainterTestServer) addCniPod(t *testing.T, node string, markReady bool) { 142 s.addPod(t, node, markReady, cniPodLabels, systemNS) 143 } 144 145 func (s *nodeTainterTestServer) addPod(t *testing.T, node string, markReady bool, labels map[string]string, ns string) { 146 t.Helper() 147 ip := "1.2.3.4" 148 name := "istio-cni-" + node 149 if ns == "" { 150 ns = systemNS 151 } 152 pod := generatePod(ip, name, ns, "sa", node, labels, nil) 153 154 p := s.pc.Get(name, pod.Namespace) 155 if p == nil { 156 // Apiserver doesn't allow Create to modify the pod status; in real world it's a 2 part process 157 pod.Status = corev1.PodStatus{} 158 newPod := s.pc.Create(pod) 159 if markReady { 160 setPodReady(newPod) 161 } 162 newPod.Status.PodIP = ip 163 newPod.Status.Phase = corev1.PodRunning 164 newPod.Status.PodIPs = []corev1.PodIP{ 165 { 166 IP: ip, 167 }, 168 } 169 s.pc.UpdateStatus(newPod) 170 } else { 171 s.pc.Update(pod) 172 } 173 } 174 175 func generateNode(name string, labels map[string]string) *corev1.Node { 176 return &corev1.Node{ 177 TypeMeta: metav1.TypeMeta{ 178 Kind: "Node", 179 APIVersion: "v1", 180 }, 181 ObjectMeta: metav1.ObjectMeta{ 182 Name: name, 183 Labels: labels, 184 }, 185 } 186 } 187 188 func generatePod(ip, name, namespace, saName, node string, labels map[string]string, annotations map[string]string) *corev1.Pod { 189 automount := false 190 return &corev1.Pod{ 191 ObjectMeta: metav1.ObjectMeta{ 192 Name: name, 193 Labels: labels, 194 Annotations: annotations, 195 Namespace: namespace, 196 }, 197 Spec: corev1.PodSpec{ 198 ServiceAccountName: saName, 199 NodeName: node, 200 AutomountServiceAccountToken: &automount, 201 // Validation requires this 202 Containers: []corev1.Container{ 203 { 204 Name: "test", 205 Image: "ununtu", 206 }, 207 }, 208 }, 209 // The cache controller uses this as key, required by our impl. 210 Status: corev1.PodStatus{ 211 Conditions: []corev1.PodCondition{ 212 { 213 Type: corev1.PodReady, 214 Status: corev1.ConditionTrue, 215 LastTransitionTime: metav1.Now(), 216 }, 217 }, 218 PodIP: ip, 219 HostIP: ip, 220 PodIPs: []corev1.PodIP{ 221 { 222 IP: ip, 223 }, 224 }, 225 Phase: corev1.PodRunning, 226 }, 227 } 228 } 229 230 func setPodReady(pod *corev1.Pod) { 231 pod.Status.Conditions = []corev1.PodCondition{ 232 { 233 Type: corev1.PodReady, 234 Status: corev1.ConditionTrue, 235 LastTransitionTime: metav1.Now(), 236 }, 237 } 238 }