istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/serviceregistry/kube/controller/namespacecontroller_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 controller 16 17 import ( 18 "context" 19 "fmt" 20 "reflect" 21 "testing" 22 "time" 23 24 v1 "k8s.io/api/core/v1" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/client-go/kubernetes" 27 28 meshconfig "istio.io/api/mesh/v1alpha1" 29 "istio.io/istio/pilot/pkg/keycertbundle" 30 "istio.io/istio/pkg/config/constants" 31 "istio.io/istio/pkg/config/mesh" 32 "istio.io/istio/pkg/kube" 33 "istio.io/istio/pkg/kube/inject" 34 "istio.io/istio/pkg/kube/kclient" 35 filter "istio.io/istio/pkg/kube/namespace" 36 "istio.io/istio/pkg/test" 37 "istio.io/istio/pkg/test/util/retry" 38 ) 39 40 func TestNamespaceController(t *testing.T) { 41 client := kube.NewFakeClient() 42 t.Cleanup(client.Shutdown) 43 watcher := keycertbundle.NewWatcher() 44 caBundle := []byte("caBundle") 45 watcher.SetAndNotify(nil, nil, caBundle) 46 meshWatcher := mesh.NewTestWatcher(&meshconfig.MeshConfig{}) 47 stop := test.NewStop(t) 48 discoveryNamespacesFilter := filter.NewDiscoveryNamespacesFilter( 49 kclient.New[*v1.Namespace](client), 50 meshWatcher, 51 stop, 52 ) 53 kube.SetObjectFilter(client, discoveryNamespacesFilter) 54 nc := NewNamespaceController(client, watcher) 55 client.RunAndWait(stop) 56 go nc.Run(stop) 57 retry.UntilOrFail(t, nc.queue.HasSynced) 58 59 expectedData := map[string]string{ 60 constants.CACertNamespaceConfigMapDataName: string(caBundle), 61 } 62 createNamespace(t, client.Kube(), "foo", nil) 63 expectConfigMap(t, nc.configmaps, CACertNamespaceConfigMap, "foo", expectedData) 64 65 // Make sure random configmap does not get updated 66 cmData := createConfigMap(t, client.Kube(), "not-root", "foo", "k") 67 expectConfigMap(t, nc.configmaps, "not-root", "foo", cmData) 68 69 newCaBundle := []byte("caBundle-new") 70 watcher.SetAndNotify(nil, nil, newCaBundle) 71 newData := map[string]string{ 72 constants.CACertNamespaceConfigMapDataName: string(newCaBundle), 73 } 74 expectConfigMap(t, nc.configmaps, CACertNamespaceConfigMap, "foo", newData) 75 76 deleteConfigMap(t, client.Kube(), "foo") 77 expectConfigMap(t, nc.configmaps, CACertNamespaceConfigMap, "foo", newData) 78 79 ignoredNamespaces := inject.IgnoredNamespaces.Copy().Delete(constants.KubeSystemNamespace) 80 for _, namespace := range ignoredNamespaces.UnsortedList() { 81 // Create namespace in ignored list, make sure its not created 82 createNamespace(t, client.Kube(), namespace, newData) 83 // Configmap in that namespace should not do anything either 84 createConfigMap(t, client.Kube(), "not-root", namespace, "k") 85 expectConfigMapNotExist(t, nc.configmaps, namespace) 86 } 87 } 88 89 func TestNamespaceControllerWithDiscoverySelectors(t *testing.T) { 90 client := kube.NewFakeClient() 91 t.Cleanup(client.Shutdown) 92 watcher := keycertbundle.NewWatcher() 93 caBundle := []byte("caBundle") 94 watcher.SetAndNotify(nil, nil, caBundle) 95 meshWatcher := mesh.NewTestWatcher(&meshconfig.MeshConfig{ 96 DiscoverySelectors: []*metav1.LabelSelector{ 97 { 98 MatchLabels: map[string]string{ 99 "discovery-selectors": "enabled", 100 }, 101 }, 102 }, 103 }) 104 stop := test.NewStop(t) 105 discoveryNamespacesFilter := filter.NewDiscoveryNamespacesFilter( 106 kclient.New[*v1.Namespace](client), 107 meshWatcher, 108 stop, 109 ) 110 kube.SetObjectFilter(client, discoveryNamespacesFilter) 111 nc := NewNamespaceController(client, watcher) 112 client.RunAndWait(stop) 113 go nc.Run(stop) 114 retry.UntilOrFail(t, nc.queue.HasSynced) 115 116 expectedData := map[string]string{ 117 constants.CACertNamespaceConfigMapDataName: string(caBundle), 118 } 119 120 nsA := "nsA" 121 nsB := "nsB" 122 123 // Create a namespace with discovery selector enabled 124 createNamespace(t, client.Kube(), nsA, map[string]string{"discovery-selectors": "enabled"}) 125 // Create a namespace without discovery selector enabled 126 createNamespace(t, client.Kube(), nsB, map[string]string{}) 127 expectConfigMap(t, nc.configmaps, CACertNamespaceConfigMap, nsA, expectedData) 128 // config map should not be created for discovery selector disabled namespace 129 expectConfigMapNotExist(t, nc.configmaps, nsB) 130 } 131 132 func TestNamespaceControllerDiscovery(t *testing.T) { 133 client := kube.NewFakeClient() 134 t.Cleanup(client.Shutdown) 135 watcher := keycertbundle.NewWatcher() 136 caBundle := []byte("caBundle") 137 watcher.SetAndNotify(nil, nil, caBundle) 138 meshWatcher := mesh.NewTestWatcher(&meshconfig.MeshConfig{ 139 DiscoverySelectors: []*metav1.LabelSelector{{ 140 MatchLabels: map[string]string{"kubernetes.io/metadata.name": "selected"}, 141 }}, 142 }) 143 stop := test.NewStop(t) 144 discoveryNamespacesFilter := filter.NewDiscoveryNamespacesFilter( 145 kclient.New[*v1.Namespace](client), 146 meshWatcher, 147 stop, 148 ) 149 kube.SetObjectFilter(client, discoveryNamespacesFilter) 150 nc := NewNamespaceController(client, watcher) 151 client.RunAndWait(stop) 152 go nc.Run(stop) 153 retry.UntilOrFail(t, nc.queue.HasSynced) 154 155 expectedData := map[string]string{ 156 constants.CACertNamespaceConfigMapDataName: string(caBundle), 157 } 158 createNamespace(t, client.Kube(), "not-selected", map[string]string{"kubernetes.io/metadata.name": "not-selected"}) 159 createNamespace(t, client.Kube(), "selected", map[string]string{"kubernetes.io/metadata.name": "selected"}) 160 161 expectConfigMap(t, nc.configmaps, CACertNamespaceConfigMap, "selected", expectedData) 162 expectConfigMapNotExist(t, nc.configmaps, "not-selected") 163 164 meshWatcher.Update(&meshconfig.MeshConfig{ 165 DiscoverySelectors: []*metav1.LabelSelector{{ 166 MatchLabels: map[string]string{"kubernetes.io/metadata.name": "not-selected"}, 167 }}, 168 }, time.Second) 169 expectConfigMap(t, nc.configmaps, CACertNamespaceConfigMap, "not-selected", expectedData) 170 expectConfigMapNotExist(t, nc.configmaps, "selected") 171 } 172 173 func deleteConfigMap(t *testing.T, client kubernetes.Interface, ns string) { 174 t.Helper() 175 _, err := client.CoreV1().ConfigMaps(ns).Get(context.TODO(), CACertNamespaceConfigMap, metav1.GetOptions{}) 176 if err != nil { 177 t.Fatal(err) 178 } 179 if err := client.CoreV1().ConfigMaps(ns).Delete(context.TODO(), CACertNamespaceConfigMap, metav1.DeleteOptions{}); err != nil { 180 t.Fatal(err) 181 } 182 } 183 184 func createConfigMap(t *testing.T, client kubernetes.Interface, name, ns, key string) map[string]string { 185 t.Helper() 186 data := map[string]string{key: "v"} 187 _, err := client.CoreV1().ConfigMaps(ns).Create(context.Background(), &v1.ConfigMap{ 188 ObjectMeta: metav1.ObjectMeta{ 189 Name: name, 190 Namespace: ns, 191 }, 192 Data: data, 193 }, metav1.CreateOptions{}) 194 if err != nil { 195 t.Fatal(err) 196 } 197 return data 198 } 199 200 func createNamespace(t *testing.T, client kubernetes.Interface, ns string, labels map[string]string) { 201 t.Helper() 202 if _, err := client.CoreV1().Namespaces().Create(context.TODO(), &v1.Namespace{ 203 ObjectMeta: metav1.ObjectMeta{Name: ns, Labels: labels}, 204 }, metav1.CreateOptions{}); err != nil { 205 t.Fatal(err) 206 } 207 } 208 209 func updateNamespace(t *testing.T, client kubernetes.Interface, ns string, labels map[string]string) { 210 t.Helper() 211 if _, err := client.CoreV1().Namespaces().Update(context.TODO(), &v1.Namespace{ 212 ObjectMeta: metav1.ObjectMeta{Name: ns, Labels: labels}, 213 }, metav1.UpdateOptions{}); err != nil { 214 t.Fatal(err) 215 } 216 } 217 218 // nolint:unparam 219 func expectConfigMap(t *testing.T, configmaps kclient.Client[*v1.ConfigMap], name, ns string, data map[string]string) { 220 t.Helper() 221 retry.UntilSuccessOrFail(t, func() error { 222 cm := configmaps.Get(name, ns) 223 if cm == nil { 224 return fmt.Errorf("not found") 225 } 226 if !reflect.DeepEqual(cm.Data, data) { 227 return fmt.Errorf("data mismatch, expected %+v got %+v", data, cm.Data) 228 } 229 return nil 230 }, retry.Timeout(time.Second*10)) 231 } 232 233 func expectConfigMapNotExist(t *testing.T, configmaps kclient.Client[*v1.ConfigMap], ns string) { 234 t.Helper() 235 err := retry.Until(func() bool { 236 cm := configmaps.Get(CACertNamespaceConfigMap, ns) 237 return cm != nil 238 }, retry.Timeout(time.Millisecond*25)) 239 240 if err == nil { 241 t.Fatalf("%s namespace should not have istio-ca-root-cert configmap.", ns) 242 } 243 }