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  }