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 }