github.com/kiali/kiali@v1.84.0/kubernetes/cache/kube_cache_test.go (about)

     1  package cache
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/require"
    10  	networking_v1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1"
    11  	apps_v1 "k8s.io/api/apps/v1"
    12  	core_v1 "k8s.io/api/core/v1"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/apimachinery/pkg/runtime"
    15  
    16  	"github.com/kiali/kiali/config"
    17  	"github.com/kiali/kiali/kubernetes"
    18  	"github.com/kiali/kiali/kubernetes/kubetest"
    19  )
    20  
    21  const IstioAPIEnabled = true
    22  
    23  func newTestingKubeCache(t *testing.T, cfg *config.Config, objects ...runtime.Object) *kubeCache {
    24  	t.Helper()
    25  
    26  	kubeCache, err := NewKubeCache(kubetest.NewFakeK8sClient(objects...), *cfg)
    27  	if err != nil {
    28  		t.Fatalf("Unable to create kube cache for testing. Err: %s", err)
    29  	}
    30  	return kubeCache
    31  }
    32  
    33  func TestNewKialiCache_isCached(t *testing.T) {
    34  	assert := assert.New(t)
    35  
    36  	conf := config.NewConfig()
    37  	conf.Deployment.AccessibleNamespaces = []string{
    38  		"bookinfo",
    39  		"a",
    40  		"abcdefghi",
    41  		"galicia",
    42  	}
    43  
    44  	kubeCache := newTestingKubeCache(t, conf)
    45  	kubeCache.refreshDuration = 0
    46  
    47  	assert.True(kubeCache.isCached("bookinfo"))
    48  	assert.True(kubeCache.isCached("a"))
    49  	assert.True(kubeCache.isCached("abcdefghi"))
    50  	assert.False(kubeCache.isCached("b"))
    51  	assert.False(kubeCache.isCached("bbcdefghi"))
    52  	assert.True(kubeCache.isCached("galicia"))
    53  	assert.False(kubeCache.isCached(""))
    54  }
    55  
    56  func TestClusterScopedCacheStopped(t *testing.T) {
    57  	assert := assert.New(t)
    58  
    59  	kubeCache := newTestingKubeCache(t, config.NewConfig())
    60  
    61  	kubeCache.Stop()
    62  	select {
    63  	case <-time.After(300 * time.Millisecond):
    64  		assert.Fail("Cache should have been stopped")
    65  	case <-kubeCache.stopClusterScopedChan:
    66  	}
    67  }
    68  
    69  func TestNSScopedCacheStopped(t *testing.T) {
    70  	assert := assert.New(t)
    71  
    72  	cfg := config.NewConfig()
    73  	cfg.Deployment.AccessibleNamespaces = []string{"ns1", "ns2"}
    74  	kubeCache := newTestingKubeCache(t, cfg)
    75  
    76  	kubeCache.Stop()
    77  	for ns, stopCh := range kubeCache.stopNSChans {
    78  		select {
    79  		case <-time.After(300 * time.Millisecond):
    80  			assert.Failf("Cache for namespace: %s should have been stopped", ns)
    81  		case <-stopCh:
    82  		}
    83  	}
    84  
    85  	assert.Empty(kubeCache.nsCacheLister)
    86  }
    87  
    88  func TestRefreshClusterScoped(t *testing.T) {
    89  	assert := assert.New(t)
    90  
    91  	svc := &core_v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "svc1", Namespace: "ns1"}}
    92  	kialiCache := newTestingKubeCache(t, config.NewConfig(), svc)
    93  	kialiCache.clusterCacheLister = &cacheLister{}
    94  	oldLister := kialiCache.clusterCacheLister
    95  	kialiCache.Refresh("")
    96  	assert.NotEqual(kialiCache.clusterCacheLister, oldLister)
    97  }
    98  
    99  func TestRefreshMultipleTimesClusterScoped(t *testing.T) {
   100  	assert := assert.New(t)
   101  
   102  	kialiCache := newTestingKubeCache(t, config.NewConfig())
   103  	kialiCache.clusterCacheLister = &cacheLister{}
   104  	oldLister := kialiCache.clusterCacheLister
   105  
   106  	kialiCache.Refresh("")
   107  	kialiCache.Refresh("")
   108  	assert.NotEqual(kialiCache.clusterCacheLister, oldLister)
   109  }
   110  
   111  func TestRefreshNSScoped(t *testing.T) {
   112  	assert := assert.New(t)
   113  
   114  	cfg := config.NewConfig()
   115  	cfg.Deployment.AccessibleNamespaces = []string{"ns1", "ns2"}
   116  	cfg.Deployment.ClusterWideAccess = false
   117  	kialiCache := newTestingKubeCache(t, cfg)
   118  	kialiCache.nsCacheLister = map[string]*cacheLister{}
   119  
   120  	kialiCache.Refresh("ns1")
   121  	assert.NotEqual(kialiCache.nsCacheLister, map[string]*cacheLister{})
   122  	assert.Contains(kialiCache.nsCacheLister, "ns1")
   123  }
   124  
   125  // Other parts of the codebase assume that this kind field is present so it's important
   126  // that the cache sets it.
   127  func TestKubeGetAndListReturnKindInfo(t *testing.T) {
   128  	assert := assert.New(t)
   129  	ns := &core_v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test"}}
   130  	d := &apps_v1.Deployment{
   131  		ObjectMeta: metav1.ObjectMeta{
   132  			Name: "deployment", Namespace: "test",
   133  		},
   134  	}
   135  	kialiCache := newTestingKubeCache(t, config.NewConfig(), ns, d)
   136  	kialiCache.Refresh("test")
   137  
   138  	deploymentFromCache, err := kialiCache.GetDeployment("test", "deployment")
   139  	assert.NoError(err)
   140  	assert.Equal(kubernetes.DeploymentType, deploymentFromCache.Kind)
   141  
   142  	deploymentListFromCache, err := kialiCache.GetDeployments("test")
   143  	assert.NoError(err)
   144  	for _, deployment := range deploymentListFromCache {
   145  		assert.Equal(kubernetes.DeploymentType, deployment.Kind)
   146  	}
   147  }
   148  
   149  // Tests that when a refresh happens, the new cache must fully load before the
   150  // new object is returned.
   151  func TestConcurrentAccessDuringRefresh(t *testing.T) {
   152  	require := require.New(t)
   153  	d := &apps_v1.Deployment{
   154  		ObjectMeta: metav1.ObjectMeta{
   155  			Name: "deployment", Namespace: "test",
   156  		},
   157  	}
   158  
   159  	kialiCache := newTestingKubeCache(t, config.NewConfig(), d)
   160  	// Prime the pump with a first Refresh.
   161  	kialiCache.Refresh("test")
   162  
   163  	stop := make(chan bool)
   164  	go func() {
   165  		for {
   166  			select {
   167  			case <-stop:
   168  				return
   169  			default:
   170  				_, err := kialiCache.GetDeployment(d.Namespace, d.Name)
   171  				require.NoError(err)
   172  			}
   173  		}
   174  	}()
   175  
   176  	kialiCache.Refresh("test")
   177  	close(stop)
   178  }
   179  
   180  func TestGetSidecar(t *testing.T) {
   181  	ns := &core_v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "testing-ns"}}
   182  	sidecar := &networking_v1beta1.Sidecar{}
   183  	sidecar.Name = "moto-sidecar"
   184  	sidecar.Namespace = "testing-ns"
   185  	sidecar.Labels = map[string]string{
   186  		"app":     "bookinfo",
   187  		"version": "v1",
   188  	}
   189  
   190  	cfg := config.NewConfig()
   191  
   192  	kubeCache := newTestingKubeCache(t, cfg, ns, sidecar)
   193  
   194  	cases := map[string]struct {
   195  		selector        string
   196  		resourceType    string
   197  		namespace       string
   198  		expectedErr     error
   199  		expectedObjects []*networking_v1beta1.Sidecar
   200  	}{
   201  		"With selector that matches": {
   202  			selector:        "app=bookinfo",
   203  			resourceType:    kubernetes.Sidecars,
   204  			expectedErr:     nil,
   205  			expectedObjects: []*networking_v1beta1.Sidecar{sidecar},
   206  		},
   207  		"With selector that doesn't match": {
   208  			selector:        "app=anotherapp",
   209  			resourceType:    kubernetes.Sidecars,
   210  			expectedErr:     nil,
   211  			expectedObjects: []*networking_v1beta1.Sidecar{},
   212  		},
   213  		"Without selector": {
   214  			resourceType:    kubernetes.Sidecars,
   215  			expectedErr:     nil,
   216  			expectedObjects: []*networking_v1beta1.Sidecar{sidecar},
   217  		},
   218  		"With unparseable selector": {
   219  			selector:        "unpar$ablestr!ng!",
   220  			resourceType:    kubernetes.Sidecars,
   221  			expectedErr:     fmt.Errorf("Any"),
   222  			expectedObjects: []*networking_v1beta1.Sidecar{},
   223  		},
   224  		"With unknown type": {
   225  			selector:        "unpar$ablestr!ng!",
   226  			resourceType:    "unknowntype",
   227  			expectedErr:     fmt.Errorf("Any"),
   228  			expectedObjects: []*networking_v1beta1.Sidecar{},
   229  		},
   230  		"Uncached namespace returns empty": {
   231  			namespace:       "uncachednamespace",
   232  			resourceType:    kubernetes.Sidecars,
   233  			expectedErr:     nil,
   234  			expectedObjects: []*networking_v1beta1.Sidecar{},
   235  		},
   236  	}
   237  
   238  	for name, tc := range cases {
   239  		t.Run(name, func(t *testing.T) {
   240  			assert := assert.New(t)
   241  
   242  			namespace := sidecar.Namespace
   243  			if tc.namespace != "" {
   244  				namespace = tc.namespace
   245  			}
   246  
   247  			objects, err := kubeCache.GetSidecars(namespace, tc.selector)
   248  			if tc.expectedErr != nil {
   249  				assert.Error(err)
   250  			} else {
   251  				assert.NoError(err)
   252  			}
   253  			assert.Equal(len(tc.expectedObjects), len(objects))
   254  		})
   255  	}
   256  }
   257  
   258  // Other parts of the codebase assume that this kind field is present so it's important
   259  // that the cache sets it.
   260  func TestGetAndListReturnKindInfo(t *testing.T) {
   261  	assert := assert.New(t)
   262  	require := require.New(t)
   263  	ns := &core_v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test"}}
   264  	vs := &networking_v1beta1.VirtualService{
   265  		ObjectMeta: metav1.ObjectMeta{
   266  			Name: "vs", Namespace: "test",
   267  		},
   268  	}
   269  
   270  	cfg := config.NewConfig()
   271  	kialiCache := newTestingKubeCache(t, cfg, ns, vs)
   272  
   273  	vsFromCache, err := kialiCache.GetVirtualService("test", "vs")
   274  	require.NoError(err)
   275  	assert.Equal(kubernetes.VirtualServiceType, vsFromCache.Kind)
   276  
   277  	vsListFromCache, err := kialiCache.GetVirtualServices("test", "")
   278  	require.NoError(err)
   279  	for _, vs := range vsListFromCache {
   280  		assert.Equal(kubernetes.VirtualServiceType, vs.Kind)
   281  	}
   282  }
   283  
   284  func TestUpdatingClientRefreshesCache(t *testing.T) {
   285  	assert := assert.New(t)
   286  	require := require.New(t)
   287  
   288  	ns := &core_v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test"}}
   289  	pod := &core_v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "test"}}
   290  
   291  	cfg := config.NewConfig()
   292  	kialiCache := newTestingKubeCache(t, cfg, ns, pod)
   293  	kialiCache.clusterCacheLister = &cacheLister{}
   294  
   295  	err := kialiCache.UpdateClient(kubetest.NewFakeK8sClient(ns, pod))
   296  	require.NoError(err)
   297  
   298  	assert.NotEqual(kialiCache.clusterCacheLister, &cacheLister{})
   299  
   300  	pods, err := kialiCache.GetPods("test", "")
   301  	require.NoError(err)
   302  	require.Len(pods, 1)
   303  }
   304  
   305  func TestIstioAPIDisabled(t *testing.T) {
   306  	assert := assert.New(t)
   307  	ns := &core_v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test"}}
   308  
   309  	cfg := config.NewConfig()
   310  	fakeClient := kubetest.NewFakeK8sClient(ns)
   311  	fakeClient.IstioAPIEnabled = false
   312  	kubeCache, err := NewKubeCache(fakeClient, *cfg)
   313  	if err != nil {
   314  		t.Fatalf("Unable to create kube cache for testing. Err: %s", err)
   315  	}
   316  
   317  	_, err = kubeCache.GetVirtualServices("test", "app=bookinfo")
   318  
   319  	assert.Error(err)
   320  }
   321  
   322  func ListingIstioObjectsWorksAcrossNamespacesWhenNamespaceScoped(t *testing.T) {
   323  	assert := assert.New(t)
   324  	require := require.New(t)
   325  
   326  	nsAlpha := &core_v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "alpha"}}
   327  	nsBeta := &core_v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "beta"}}
   328  	vsAlpha := &networking_v1beta1.VirtualService{
   329  		ObjectMeta: metav1.ObjectMeta{
   330  			Name: "test-alpha", Namespace: "alpha",
   331  		},
   332  	}
   333  	vsBeta := &networking_v1beta1.VirtualService{
   334  		ObjectMeta: metav1.ObjectMeta{
   335  			Name: "test-beta", Namespace: "beta",
   336  		},
   337  	}
   338  
   339  	cfg := config.NewConfig()
   340  	cfg.Deployment.AccessibleNamespaces = []string{"alpha", "beta"}
   341  	cfg.Deployment.ClusterWideAccess = false
   342  	kubeCache := newTestingKubeCache(t, cfg, nsAlpha, nsBeta, vsAlpha, vsBeta)
   343  
   344  	vsList, err := kubeCache.GetVirtualServices("", "")
   345  	require.NoError(err)
   346  	assert.Len(vsList, 2)
   347  }