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 }