istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/kube/kclient/index_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 kclient
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  	"time"
    21  
    22  	"go.uber.org/atomic"
    23  	corev1 "k8s.io/api/core/v1"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/types"
    26  
    27  	"istio.io/istio/pkg/kube"
    28  	"istio.io/istio/pkg/kube/controllers"
    29  	"istio.io/istio/pkg/test"
    30  	"istio.io/istio/pkg/test/util/assert"
    31  	"istio.io/istio/pkg/test/util/retry"
    32  )
    33  
    34  type SaNode struct {
    35  	ServiceAccount types.NamespacedName
    36  	Node           string
    37  }
    38  
    39  func TestIndex(t *testing.T) {
    40  	c := kube.NewFakeClient()
    41  	pods := New[*corev1.Pod](c)
    42  	c.RunAndWait(test.NewStop(t))
    43  	index := CreateIndex[SaNode, *corev1.Pod](pods, func(pod *corev1.Pod) []SaNode {
    44  		if len(pod.Spec.NodeName) == 0 {
    45  			return nil
    46  		}
    47  		if len(pod.Spec.ServiceAccountName) == 0 {
    48  			return nil
    49  		}
    50  		return []SaNode{{
    51  			ServiceAccount: types.NamespacedName{
    52  				Namespace: pod.Namespace,
    53  				Name:      pod.Spec.ServiceAccountName,
    54  			},
    55  			Node: pod.Spec.NodeName,
    56  		}}
    57  	})
    58  	k1 := SaNode{
    59  		ServiceAccount: types.NamespacedName{
    60  			Namespace: "ns",
    61  			Name:      "sa",
    62  		},
    63  		Node: "node",
    64  	}
    65  	k2 := SaNode{
    66  		ServiceAccount: types.NamespacedName{
    67  			Namespace: "ns",
    68  			Name:      "sa2",
    69  		},
    70  		Node: "node",
    71  	}
    72  	assert.Equal(t, index.Lookup(k1), nil)
    73  	assert.Equal(t, index.Lookup(k2), nil)
    74  	pod1 := &corev1.Pod{
    75  		ObjectMeta: metav1.ObjectMeta{
    76  			Name:      "pod",
    77  			Namespace: "ns",
    78  		},
    79  		Spec: corev1.PodSpec{
    80  			ServiceAccountName: "sa",
    81  			NodeName:           "node",
    82  		},
    83  	}
    84  	pod2 := &corev1.Pod{
    85  		ObjectMeta: metav1.ObjectMeta{
    86  			Name:      "pod2",
    87  			Namespace: "ns",
    88  		},
    89  		Spec: corev1.PodSpec{
    90  			ServiceAccountName: "sa2",
    91  			NodeName:           "node",
    92  		},
    93  	}
    94  	pod3 := &corev1.Pod{
    95  		ObjectMeta: metav1.ObjectMeta{
    96  			Name:      "pod3",
    97  			Namespace: "ns",
    98  		},
    99  		Spec: corev1.PodSpec{
   100  			ServiceAccountName: "sa",
   101  			NodeName:           "node",
   102  		},
   103  	}
   104  
   105  	assertIndex := func(k SaNode, pods ...*corev1.Pod) {
   106  		t.Helper()
   107  		assert.EventuallyEqual(t, func() []*corev1.Pod { return index.Lookup(k) }, pods, retry.Timeout(time.Second*5))
   108  	}
   109  
   110  	// When we create a pod, we should (eventually) see it in the index
   111  	c.Kube().CoreV1().Pods("ns").Create(context.Background(), pod1, metav1.CreateOptions{})
   112  	assertIndex(k1, pod1)
   113  	assertIndex(k2)
   114  
   115  	// Create another pod; we ought to find it as well now.
   116  	c.Kube().CoreV1().Pods("ns").Create(context.Background(), pod2, metav1.CreateOptions{})
   117  	assertIndex(k1, pod1) // Original one must still persist
   118  	assertIndex(k2, pod2) // New one should be there, eventually
   119  
   120  	// Create another pod with the same SA; we ought to find multiple now.
   121  	c.Kube().CoreV1().Pods("ns").Create(context.Background(), pod3, metav1.CreateOptions{})
   122  	assertIndex(k1, pod1, pod3) // Original one must still persist
   123  	assertIndex(k2, pod2)       // New one should be there, eventually
   124  
   125  	pod1Alt := pod1.DeepCopy()
   126  	// This can't happen in practice with Pod, but Index supports arbitrary types
   127  	pod1Alt.Spec.ServiceAccountName = "new-sa"
   128  
   129  	keyNew := SaNode{
   130  		ServiceAccount: types.NamespacedName{
   131  			Namespace: "ns",
   132  			Name:      "new-sa",
   133  		},
   134  		Node: "node",
   135  	}
   136  	c.Kube().CoreV1().Pods("ns").Update(context.Background(), pod1Alt, metav1.UpdateOptions{})
   137  	assertIndex(k1, pod3)        // Pod should be dropped from the index
   138  	assertIndex(keyNew, pod1Alt) // And added under the new key
   139  
   140  	c.Kube().CoreV1().Pods("ns").Delete(context.Background(), pod1Alt.Name, metav1.DeleteOptions{})
   141  	assertIndex(k1, pod3) // Shouldn't impact others
   142  	assertIndex(keyNew)   // but should be removed
   143  
   144  	// Should fully cleanup the index on deletes
   145  	c.Kube().CoreV1().Pods("ns").Delete(context.Background(), pod2.Name, metav1.DeleteOptions{})
   146  	c.Kube().CoreV1().Pods("ns").Delete(context.Background(), pod3.Name, metav1.DeleteOptions{})
   147  	assert.EventuallyEqual(t, func() int {
   148  		index.mu.RLock()
   149  		defer index.mu.RUnlock()
   150  		return len(index.objects)
   151  	}, 0)
   152  }
   153  
   154  func TestIndexDelegate(t *testing.T) {
   155  	c := kube.NewFakeClient()
   156  	pods := New[*corev1.Pod](c)
   157  	c.RunAndWait(test.NewStop(t))
   158  	var index *Index[string, *corev1.Pod]
   159  	adds := atomic.NewInt32(0)
   160  	index = CreateIndexWithDelegate[string, *corev1.Pod](pods, func(pod *corev1.Pod) []string {
   161  		return []string{pod.Spec.ServiceAccountName}
   162  	}, controllers.EventHandler[*corev1.Pod]{
   163  		AddFunc: func(obj *corev1.Pod) {
   164  			// Assert that our handler sees the incoming update (and doesn't run before)
   165  			sa := obj.Spec.ServiceAccountName
   166  			got := index.Lookup(sa)
   167  			for _, p := range got {
   168  				if p.Name == obj.Name {
   169  					adds.Inc()
   170  					return
   171  				}
   172  			}
   173  			t.Fatalf("pod %v/%v not found in index, have %v", obj.Name, sa, got)
   174  		},
   175  	})
   176  	pod1 := &corev1.Pod{
   177  		ObjectMeta: metav1.ObjectMeta{
   178  			Name:      "pod",
   179  			Namespace: "ns",
   180  		},
   181  		Spec: corev1.PodSpec{
   182  			ServiceAccountName: "sa",
   183  			NodeName:           "node",
   184  		},
   185  	}
   186  	pod2 := &corev1.Pod{
   187  		ObjectMeta: metav1.ObjectMeta{
   188  			Name:      "pod2",
   189  			Namespace: "ns",
   190  		},
   191  		Spec: corev1.PodSpec{
   192  			ServiceAccountName: "sa2",
   193  			NodeName:           "node",
   194  		},
   195  	}
   196  
   197  	c.Kube().CoreV1().Pods("ns").Create(context.Background(), pod1, metav1.CreateOptions{})
   198  	assert.EventuallyEqual(t, adds.Load, 1)
   199  
   200  	c.Kube().CoreV1().Pods("ns").Create(context.Background(), pod2, metav1.CreateOptions{})
   201  	assert.EventuallyEqual(t, adds.Load, 2)
   202  }