istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/mesh/kubemesh/watcher_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 kubemesh 16 17 import ( 18 "context" 19 "fmt" 20 "sync" 21 "testing" 22 "time" 23 24 "github.com/google/go-cmp/cmp" 25 . "github.com/onsi/gomega" 26 "go.uber.org/atomic" 27 "google.golang.org/protobuf/testing/protocmp" 28 v1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 corev1 "k8s.io/client-go/kubernetes/typed/core/v1" 31 32 meshconfig "istio.io/api/mesh/v1alpha1" 33 "istio.io/istio/pkg/config/mesh" 34 "istio.io/istio/pkg/kube" 35 "istio.io/istio/pkg/test" 36 "istio.io/istio/pkg/test/util/assert" 37 "istio.io/istio/pkg/test/util/retry" 38 ) 39 40 const ( 41 namespace string = "istio-system" 42 name string = "istio" 43 key string = "MeshConfig" 44 ) 45 46 func makeConfigMapWithName(name, resourceVersion string, data map[string]string) *v1.ConfigMap { 47 return &v1.ConfigMap{ 48 ObjectMeta: metav1.ObjectMeta{ 49 Namespace: namespace, 50 Name: name, 51 ResourceVersion: resourceVersion, 52 }, 53 Data: data, 54 } 55 } 56 57 func makeConfigMap(resourceVersion string, data map[string]string) *v1.ConfigMap { 58 return makeConfigMapWithName(name, resourceVersion, data) 59 } 60 61 func TestExtraConfigmap(t *testing.T) { 62 extraCmName := "extra" 63 64 cmCore := makeConfigMap("1", map[string]string{ 65 key: "ingressClass: core", 66 }) 67 cmUser := makeConfigMapWithName(extraCmName, "1", map[string]string{ 68 key: "ingressClass: user", 69 }) 70 cmUserinvalid := makeConfigMapWithName(extraCmName, "1", map[string]string{ 71 key: "ingressClass: 1", 72 }) 73 setup := func(t test.Failer) (corev1.ConfigMapInterface, mesh.Watcher) { 74 client := kube.NewFakeClient() 75 cms := client.Kube().CoreV1().ConfigMaps(namespace) 76 stop := test.NewStop(t) 77 w := NewConfigMapWatcher(client, namespace, name, key, true, stop) 78 AddUserMeshConfig(client, w, namespace, key, extraCmName, stop) 79 client.RunAndWait(stop) 80 return cms, w 81 } 82 83 t.Run("core first", func(t *testing.T) { 84 cms, w := setup(t) 85 if _, err := cms.Create(context.Background(), cmCore, metav1.CreateOptions{}); err != nil { 86 t.Fatal(err) 87 } 88 if _, err := cms.Create(context.Background(), cmUser, metav1.CreateOptions{}); err != nil { 89 t.Fatal(err) 90 } 91 retry.UntilOrFail(t, func() bool { return w.Mesh().GetIngressClass() == "core" }, retry.Delay(time.Millisecond), retry.Timeout(time.Second)) 92 }) 93 t.Run("user first", func(t *testing.T) { 94 cms, w := setup(t) 95 if _, err := cms.Create(context.Background(), cmUser, metav1.CreateOptions{}); err != nil { 96 t.Fatal(err) 97 } 98 if _, err := cms.Create(context.Background(), cmCore, metav1.CreateOptions{}); err != nil { 99 t.Fatal(err) 100 } 101 retry.UntilOrFail(t, func() bool { return w.Mesh().GetIngressClass() == "core" }, retry.Delay(time.Millisecond), retry.Timeout(time.Second)) 102 }) 103 t.Run("only user", func(t *testing.T) { 104 cms, w := setup(t) 105 if _, err := cms.Create(context.Background(), cmUser, metav1.CreateOptions{}); err != nil { 106 t.Fatal(err) 107 } 108 retry.UntilOrFail(t, func() bool { return w.Mesh().GetIngressClass() == "user" }, retry.Delay(time.Millisecond), retry.Timeout(time.Second)) 109 }) 110 t.Run("only core", func(t *testing.T) { 111 cms, w := setup(t) 112 if _, err := cms.Create(context.Background(), cmCore, metav1.CreateOptions{}); err != nil { 113 t.Fatal(err) 114 } 115 retry.UntilOrFail(t, func() bool { return w.Mesh().GetIngressClass() == "core" }, retry.Delay(time.Millisecond), retry.Timeout(time.Second)) 116 }) 117 t.Run("invalid user config", func(t *testing.T) { 118 cms, w := setup(t) 119 if _, err := cms.Create(context.Background(), cmCore, metav1.CreateOptions{}); err != nil { 120 t.Fatal(err) 121 } 122 if _, err := cms.Create(context.Background(), cmUserinvalid, metav1.CreateOptions{}); err != nil { 123 t.Fatal(err) 124 } 125 retry.UntilOrFail(t, func() bool { return w.Mesh().GetIngressClass() == "core" }, retry.Delay(time.Millisecond), retry.Timeout(time.Second)) 126 }) 127 t.Run("many updates", func(t *testing.T) { 128 cms, w := setup(t) 129 rev := atomic.NewInt32(1) 130 mkMap := func(m, d string) *v1.ConfigMap { 131 mm := makeConfigMapWithName(m, "1", map[string]string{ 132 key: fmt.Sprintf(`ingressClass: "%s"`, d), 133 }) 134 mm.ResourceVersion = fmt.Sprint(rev.Inc()) 135 return mm 136 } 137 if _, err := cms.Create(context.Background(), mkMap(extraCmName, "init"), metav1.CreateOptions{}); err != nil { 138 t.Fatal(err) 139 } 140 if _, err := cms.Create(context.Background(), mkMap(name, "init"), metav1.CreateOptions{}); err != nil { 141 t.Fatal(err) 142 } 143 retry.UntilOrFail(t, func() bool { return w.Mesh().GetIngressClass() == "init" }, retry.Delay(time.Millisecond), retry.Timeout(time.Second)) 144 errCh := make(chan error, 2) 145 for i := 0; i < 100; i++ { 146 t.Log("iter", i) 147 write := fmt.Sprint(i) 148 wg := sync.WaitGroup{} 149 wg.Add(2) 150 go func() { 151 defer wg.Done() 152 if _, err := cms.Update(context.Background(), mkMap(extraCmName, write), metav1.UpdateOptions{}); err != nil { 153 errCh <- err 154 } 155 }() 156 go func() { 157 defer wg.Done() 158 if _, err := cms.Update(context.Background(), mkMap(name, write), metav1.UpdateOptions{}); err != nil { 159 errCh <- err 160 } 161 }() 162 wg.Wait() 163 assert.EventuallyEqual(t, func() string { 164 return w.Mesh().GetIngressClass() 165 }, write, 166 retry.Delay(time.Millisecond), 167 retry.Timeout(time.Second*5), 168 retry.Message("write failed "+write), 169 ) 170 select { 171 case err := <-errCh: 172 t.Fatal(err) 173 default: 174 } 175 } 176 select { 177 case err := <-errCh: 178 t.Fatal(err) 179 default: 180 } 181 }) 182 } 183 184 func TestNewConfigMapWatcher(t *testing.T) { 185 yaml := "trustDomain: something.new" 186 m, err := mesh.ApplyMeshConfigDefaults(yaml) 187 if err != nil { 188 t.Fatal(err) 189 } 190 191 cm := makeConfigMap("1", map[string]string{ 192 key: yaml, 193 }) 194 badCM := makeConfigMap("2", map[string]string{ 195 "other-key": yaml, 196 }) 197 badCM2 := makeConfigMap("3", map[string]string{ 198 key: "bad yaml", 199 }) 200 201 client := kube.NewFakeClient() 202 cms := client.Kube().CoreV1().ConfigMaps(namespace) 203 stop := test.NewStop(t) 204 w := NewConfigMapWatcher(client, namespace, name, key, false, stop) 205 client.RunAndWait(stop) 206 207 var mu sync.Mutex 208 newM := mesh.DefaultMeshConfig() 209 w.AddMeshHandler(func() { 210 mu.Lock() 211 defer mu.Unlock() 212 newM = w.Mesh() 213 }) 214 215 steps := []struct { 216 added *v1.ConfigMap 217 updated *v1.ConfigMap 218 deleted *v1.ConfigMap 219 220 expect *meshconfig.MeshConfig 221 }{ 222 {expect: mesh.DefaultMeshConfig()}, 223 {added: cm, expect: m}, 224 225 // Handle misconfiguration errors. 226 {updated: badCM, expect: m}, 227 {updated: cm, expect: m}, 228 {updated: badCM2, expect: m}, 229 {updated: badCM, expect: m}, 230 {updated: cm, expect: m}, 231 232 {deleted: cm, expect: mesh.DefaultMeshConfig()}, 233 {added: badCM, expect: mesh.DefaultMeshConfig()}, 234 } 235 236 for i, step := range steps { 237 t.Run(fmt.Sprintf("[%v]", i), func(t *testing.T) { 238 g := NewWithT(t) 239 240 switch { 241 case step.added != nil: 242 _, err := cms.Create(context.TODO(), step.added, metav1.CreateOptions{}) 243 g.Expect(err).Should(BeNil()) 244 case step.updated != nil: 245 _, err := cms.Update(context.TODO(), step.updated, metav1.UpdateOptions{}) 246 g.Expect(err).Should(BeNil()) 247 case step.deleted != nil: 248 g.Expect(cms.Delete(context.TODO(), step.deleted.Name, metav1.DeleteOptions{})). 249 Should(Succeed()) 250 } 251 252 retry.UntilOrFail(t, func() bool { return cmp.Equal(w.Mesh(), step.expect, protocmp.Transform()) }) 253 retry.UntilOrFail(t, func() bool { 254 mu.Lock() 255 defer mu.Unlock() 256 return cmp.Equal(newM, step.expect, protocmp.Transform()) 257 }) 258 }) 259 } 260 }