istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/config/kube/ingress/controller_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 ingress 16 17 import ( 18 "testing" 19 "time" 20 21 corev1 "k8s.io/api/core/v1" 22 net "k8s.io/api/networking/v1" 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 25 meshconfig "istio.io/api/mesh/v1alpha1" 26 "istio.io/istio/pilot/pkg/model" 27 kubecontroller "istio.io/istio/pilot/pkg/serviceregistry/kube/controller" 28 "istio.io/istio/pkg/config" 29 "istio.io/istio/pkg/config/mesh" 30 "istio.io/istio/pkg/config/schema/gvk" 31 "istio.io/istio/pkg/kube" 32 "istio.io/istio/pkg/kube/kclient/clienttest" 33 "istio.io/istio/pkg/util/sets" 34 ) 35 36 func newFakeController() (model.ConfigStoreController, kube.Client) { 37 meshHolder := mesh.NewTestWatcher(&meshconfig.MeshConfig{ 38 IngressControllerMode: meshconfig.MeshConfig_DEFAULT, 39 }) 40 fakeClient := kube.NewFakeClient() 41 return NewController(fakeClient, meshHolder, kubecontroller.Options{}), fakeClient 42 } 43 44 func TestIngressController(t *testing.T) { 45 ingress1 := net.Ingress{ 46 ObjectMeta: metav1.ObjectMeta{ 47 Namespace: "mock", // goes into backend full name 48 Name: "test", 49 }, 50 Spec: net.IngressSpec{ 51 Rules: []net.IngressRule{ 52 { 53 Host: "my.host.com", 54 IngressRuleValue: net.IngressRuleValue{ 55 HTTP: &net.HTTPIngressRuleValue{ 56 Paths: []net.HTTPIngressPath{ 57 { 58 Path: "/test", 59 Backend: net.IngressBackend{ 60 Service: &net.IngressServiceBackend{ 61 Name: "foo", 62 Port: net.ServiceBackendPort{ 63 Number: 8000, 64 }, 65 }, 66 }, 67 }, 68 }, 69 }, 70 }, 71 }, 72 { 73 Host: "my2.host.com", 74 IngressRuleValue: net.IngressRuleValue{ 75 HTTP: &net.HTTPIngressRuleValue{ 76 Paths: []net.HTTPIngressPath{ 77 { 78 Path: "/test1.*", 79 Backend: net.IngressBackend{ 80 Service: &net.IngressServiceBackend{ 81 Name: "bar", 82 Port: net.ServiceBackendPort{ 83 Number: 8000, 84 }, 85 }, 86 }, 87 }, 88 }, 89 }, 90 }, 91 }, 92 { 93 Host: "my3.host.com", 94 IngressRuleValue: net.IngressRuleValue{ 95 HTTP: &net.HTTPIngressRuleValue{ 96 Paths: []net.HTTPIngressPath{ 97 { 98 Path: "/test/*", 99 Backend: net.IngressBackend{ 100 Service: &net.IngressServiceBackend{ 101 Name: "bar", 102 Port: net.ServiceBackendPort{ 103 Number: 8000, 104 }, 105 }, 106 }, 107 }, 108 }, 109 }, 110 }, 111 }, 112 }, 113 }, 114 } 115 116 ingress2 := net.Ingress{ 117 ObjectMeta: metav1.ObjectMeta{ 118 Namespace: "mock", 119 Name: "test", 120 }, 121 Spec: net.IngressSpec{ 122 Rules: []net.IngressRule{ 123 { 124 Host: "my.host.com", 125 IngressRuleValue: net.IngressRuleValue{ 126 HTTP: &net.HTTPIngressRuleValue{ 127 Paths: []net.HTTPIngressPath{ 128 { 129 Path: "/test2", 130 Backend: net.IngressBackend{ 131 Service: &net.IngressServiceBackend{ 132 Name: "foo", 133 Port: net.ServiceBackendPort{ 134 Number: 8000, 135 }, 136 }, 137 }, 138 }, 139 }, 140 }, 141 }, 142 }, 143 }, 144 }, 145 } 146 147 controller, client := newFakeController() 148 ingress := clienttest.NewWriter[*net.Ingress](t, client) 149 configCh := make(chan config.Config) 150 151 configHandler := func(_, curr config.Config, event model.Event) { 152 configCh <- curr 153 } 154 155 wait := func() config.Config { 156 select { 157 case x := <-configCh: 158 return x 159 case <-time.After(time.Second * 10): 160 t.Fatalf("timed out waiting for config") 161 } 162 return config.Config{} 163 } 164 165 controller.RegisterEventHandler(gvk.VirtualService, configHandler) 166 stopCh := make(chan struct{}) 167 go controller.Run(stopCh) 168 defer close(stopCh) 169 170 client.RunAndWait(stopCh) 171 172 ingress.Create(&ingress1) 173 vs := wait() 174 if vs.Name != ingress1.Name+"-"+"virtualservice" || vs.Namespace != ingress1.Namespace { 175 t.Errorf("received unecpected config %v/%v", vs.Namespace, vs.Name) 176 } 177 ingress.Update(&ingress2) 178 vs = wait() 179 if vs.Name != ingress1.Name+"-"+"virtualservice" || vs.Namespace != ingress1.Namespace { 180 t.Errorf("received unecpected config %v/%v", vs.Namespace, vs.Name) 181 } 182 } 183 184 func TestIngressControllerWithPortName(t *testing.T) { 185 ingressConfig := net.Ingress{ 186 ObjectMeta: metav1.ObjectMeta{ 187 Namespace: "mock", 188 Name: "test", 189 }, 190 Spec: net.IngressSpec{ 191 Rules: []net.IngressRule{ 192 { 193 Host: "my.host.com", 194 IngressRuleValue: net.IngressRuleValue{ 195 HTTP: &net.HTTPIngressRuleValue{ 196 Paths: []net.HTTPIngressPath{ 197 { 198 Path: "/foo", 199 Backend: net.IngressBackend{ 200 Service: &net.IngressServiceBackend{ 201 Name: "foo", 202 Port: net.ServiceBackendPort{ 203 Number: 8000, 204 }, 205 }, 206 }, 207 }, 208 }, 209 }, 210 }, 211 }, 212 { 213 Host: "my2.host.com", 214 IngressRuleValue: net.IngressRuleValue{ 215 HTTP: &net.HTTPIngressRuleValue{ 216 Paths: []net.HTTPIngressPath{ 217 { 218 Path: "/bar", 219 Backend: net.IngressBackend{ 220 Service: &net.IngressServiceBackend{ 221 Name: "bar", 222 Port: net.ServiceBackendPort{ 223 Name: "http", 224 }, 225 }, 226 }, 227 }, 228 }, 229 }, 230 }, 231 }, 232 }, 233 }, 234 } 235 236 serviceConfig := corev1.Service{ 237 ObjectMeta: metav1.ObjectMeta{ 238 Namespace: "mock", 239 Name: "bar", 240 }, 241 Spec: corev1.ServiceSpec{ 242 Ports: []corev1.ServicePort{ 243 { 244 Name: "http", 245 Port: 8080, 246 }, 247 }, 248 }, 249 } 250 251 controller, client := newFakeController() 252 ingress := clienttest.NewWriter[*net.Ingress](t, client) 253 service := clienttest.NewWriter[*corev1.Service](t, client) 254 configCh := make(chan config.Config) 255 256 configHandler := func(_, curr config.Config, event model.Event) { 257 configCh <- curr 258 } 259 260 wait := func() config.Config { 261 select { 262 case x := <-configCh: 263 return x 264 case <-time.After(time.Second * 10): 265 t.Fatalf("timed out waiting for config") 266 } 267 return config.Config{} 268 } 269 270 controller.RegisterEventHandler(gvk.VirtualService, configHandler) 271 stopCh := make(chan struct{}) 272 go controller.Run(stopCh) 273 defer close(stopCh) 274 275 client.RunAndWait(stopCh) 276 277 // First create ingress. 278 ingress.Create(&ingressConfig) 279 vs := wait() 280 if vs.Name != ingressConfig.Name+"-"+"virtualservice" || vs.Namespace != ingressConfig.Namespace { 281 t.Errorf("received unecpected config %v/%v", vs.Namespace, vs.Name) 282 } 283 284 // Then we create service. 285 service.Create(&serviceConfig) 286 vs = wait() 287 if vs.Name != ingressConfig.Name+"-"+"virtualservice" || vs.Namespace != ingressConfig.Namespace { 288 t.Errorf("received unecpected config %v/%v", vs.Namespace, vs.Name) 289 } 290 291 // We change service port number. 292 serviceConfig.Spec.Ports[0].Port = 8090 293 service.Update(&serviceConfig) 294 vs = wait() 295 if vs.Name != ingressConfig.Name+"-"+"virtualservice" || vs.Namespace != ingressConfig.Namespace { 296 t.Errorf("received unecpected config %v/%v", vs.Namespace, vs.Name) 297 } 298 } 299 300 func TestExtractServicesByPortNameType(t *testing.T) { 301 testCases := []struct { 302 name string 303 input net.Ingress 304 expect sets.String 305 }{ 306 { 307 name: "has no port name", 308 input: net.Ingress{ 309 ObjectMeta: metav1.ObjectMeta{ 310 Namespace: "ingress", 311 Name: "test", 312 }, 313 Spec: net.IngressSpec{ 314 Rules: []net.IngressRule{ 315 { 316 Host: "my.host.com", 317 IngressRuleValue: net.IngressRuleValue{ 318 HTTP: &net.HTTPIngressRuleValue{ 319 Paths: []net.HTTPIngressPath{ 320 { 321 Path: "/test", 322 Backend: net.IngressBackend{ 323 Service: &net.IngressServiceBackend{ 324 Name: "foo", 325 Port: net.ServiceBackendPort{ 326 Number: 8000, 327 }, 328 }, 329 }, 330 }, 331 }, 332 }, 333 }, 334 }, 335 }, 336 }, 337 }, 338 expect: nil, 339 }, 340 { 341 name: "has no port name", 342 input: net.Ingress{ 343 ObjectMeta: metav1.ObjectMeta{ 344 Namespace: "ingress", 345 Name: "test", 346 }, 347 Spec: net.IngressSpec{ 348 Rules: []net.IngressRule{ 349 { 350 Host: "my.host.com", 351 IngressRuleValue: net.IngressRuleValue{ 352 HTTP: &net.HTTPIngressRuleValue{ 353 Paths: []net.HTTPIngressPath{ 354 { 355 Path: "/test", 356 Backend: net.IngressBackend{ 357 Service: &net.IngressServiceBackend{ 358 Name: "foo", 359 Port: net.ServiceBackendPort{ 360 Number: 8000, 361 }, 362 }, 363 }, 364 }, 365 { 366 Path: "/bar", 367 Backend: net.IngressBackend{ 368 Service: &net.IngressServiceBackend{ 369 Name: "bar", 370 Port: net.ServiceBackendPort{ 371 Name: "http", 372 }, 373 }, 374 }, 375 }, 376 }, 377 }, 378 }, 379 }, 380 { 381 Host: "my1.host.com", 382 IngressRuleValue: net.IngressRuleValue{ 383 HTTP: &net.HTTPIngressRuleValue{ 384 Paths: []net.HTTPIngressPath{ 385 { 386 Path: "/mock", 387 Backend: net.IngressBackend{ 388 Service: &net.IngressServiceBackend{ 389 Name: "mock", 390 Port: net.ServiceBackendPort{ 391 Name: "grpc", 392 }, 393 }, 394 }, 395 }, 396 }, 397 }, 398 }, 399 }, 400 }, 401 }, 402 }, 403 expect: sets.String{}.InsertAll("ingress/bar", "ingress/mock"), 404 }, 405 } 406 407 for _, testCase := range testCases { 408 t.Run(testCase.name, func(t *testing.T) { 409 if !testCase.expect.Equals(extractServicesByPortNameType(&testCase.input)) { 410 t.Fatal("should be equal.") 411 } 412 }) 413 } 414 } 415 416 func TestExtractPorts(t *testing.T) { 417 ports := []corev1.ServicePort{ 418 { 419 Port: 80, 420 }, 421 { 422 Name: "http", 423 Port: 8080, 424 }, 425 } 426 427 expect := sets.New("80|", "8080|http") 428 if !expect.Equals(extractPorts(ports)) { 429 t.Fatal("should be equal") 430 } 431 }