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  }