github.com/cilium/cilium@v1.16.2/operator/pkg/ingress/ingress_reconcile_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package ingress 5 6 import ( 7 "context" 8 "io" 9 "testing" 10 "time" 11 12 "github.com/sirupsen/logrus" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 "google.golang.org/protobuf/types/known/anypb" 16 corev1 "k8s.io/api/core/v1" 17 networkingv1 "k8s.io/api/networking/v1" 18 k8sApiErrors "k8s.io/apimachinery/pkg/api/errors" 19 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 "k8s.io/apimachinery/pkg/types" 21 "sigs.k8s.io/controller-runtime/pkg/client" 22 "sigs.k8s.io/controller-runtime/pkg/client/fake" 23 "sigs.k8s.io/controller-runtime/pkg/client/interceptor" 24 "sigs.k8s.io/controller-runtime/pkg/reconcile" 25 26 "github.com/cilium/cilium/operator/pkg/model" 27 "github.com/cilium/cilium/operator/pkg/model/translation" 28 ingressTranslation "github.com/cilium/cilium/operator/pkg/model/translation/ingress" 29 "github.com/cilium/cilium/pkg/envoy" 30 ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" 31 ) 32 33 const ( 34 testCiliumNamespace = "cilium" 35 testUseProxyProtocol = true 36 testCiliumSecretsNamespace = "cilium-secrets" 37 testDefaultLoadbalancingServiceName = "cilium-ingress" 38 testDefaultSecretNamespace = "" 39 testDefaultSecretName = "" 40 testDefaultTimeout = 60 41 testIngressDefaultRequestTimeout = time.Duration(0) 42 ) 43 44 func TestReconcile(t *testing.T) { 45 logger := logrus.New() 46 logger.SetOutput(io.Discard) 47 48 t.Run("Reconcile of Cilium Ingress without explicit loadbalancing mode will create the resources for the default loadbalancing mode if they don't exist", func(t *testing.T) { 49 fakeClient := fake.NewClientBuilder(). 50 WithScheme(testScheme()). 51 WithObjects( 52 &networkingv1.Ingress{ 53 ObjectMeta: metav1.ObjectMeta{ 54 Namespace: "test", 55 Name: "test", 56 }, 57 Spec: networkingv1.IngressSpec{ 58 IngressClassName: model.AddressOf("cilium"), 59 DefaultBackend: defaultBackend(), 60 }, 61 }, 62 ). 63 Build() 64 65 cecTranslator := translation.NewCECTranslator(testCiliumSecretsNamespace, testUseProxyProtocol, false, false, testDefaultTimeout, false, nil, false, false, 0) 66 dedicatedIngressTranslator := ingressTranslation.NewDedicatedIngressTranslator(cecTranslator, false) 67 68 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, dedicatedIngressTranslator, testCiliumNamespace, []string{}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, false, 0) 69 70 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 71 NamespacedName: types.NamespacedName{ 72 Namespace: "test", 73 Name: "test", 74 }, 75 }) 76 require.NoError(t, err) 77 require.NotNil(t, result) 78 79 svc := corev1.Service{} 80 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &svc) 81 require.NoError(t, err, "Dedicated loadbalancer service should exist") 82 83 ep := corev1.Endpoints{} 84 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &ep) 85 require.NoError(t, err, "Dedicated loadbalancer service endpoints should exist") 86 87 cec := ciliumv2.CiliumEnvoyConfig{} 88 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test-test"}, &cec) 89 require.NoError(t, err, "Dedicated CiliumEnvoyConfig should exist") 90 91 sharedCEC := ciliumv2.CiliumEnvoyConfig{} 92 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: testCiliumNamespace, Name: testDefaultLoadbalancingServiceName}, &sharedCEC) 93 require.Error(t, err, "Empty CiliumEnvoyConfig must be removed") 94 require.True(t, k8sApiErrors.IsNotFound(err)) 95 }) 96 97 t.Run("Reconcile of Ingress without specific IngressClassName will create resources if cilium IngressClass is the default", func(t *testing.T) { 98 fakeClient := fake.NewClientBuilder(). 99 WithScheme(testScheme()). 100 WithObjects( 101 &networkingv1.Ingress{ 102 ObjectMeta: metav1.ObjectMeta{ 103 Namespace: "test", 104 Name: "test", 105 }, 106 Spec: networkingv1.IngressSpec{ 107 DefaultBackend: defaultBackend(), 108 }, 109 }, 110 &networkingv1.IngressClass{ 111 ObjectMeta: metav1.ObjectMeta{ 112 Name: "cilium", 113 Annotations: map[string]string{ 114 "ingressclass.kubernetes.io/is-default-class": "true", 115 }, 116 }, 117 }, 118 ). 119 Build() 120 121 cecTranslator := translation.NewCECTranslator(testCiliumSecretsNamespace, testUseProxyProtocol, false, false, testDefaultTimeout, false, nil, false, false, 0) 122 dedicatedIngressTranslator := ingressTranslation.NewDedicatedIngressTranslator(cecTranslator, false) 123 124 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, dedicatedIngressTranslator, testCiliumNamespace, []string{}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, false, 0) 125 126 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 127 NamespacedName: types.NamespacedName{ 128 Namespace: "test", 129 Name: "test", 130 }, 131 }) 132 require.NoError(t, err) 133 require.NotNil(t, result) 134 135 svc := corev1.Service{} 136 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &svc) 137 require.NoError(t, err, "Dedicated loadbalancer service should exist") 138 139 ep := corev1.Endpoints{} 140 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &ep) 141 require.NoError(t, err, "Dedicated loadbalancer service endpoints should exist") 142 143 cec := ciliumv2.CiliumEnvoyConfig{} 144 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test-test"}, &cec) 145 require.NoError(t, err, "Dedicated CiliumEnvoyConfig should exist") 146 147 sharedCEC := ciliumv2.CiliumEnvoyConfig{} 148 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: testCiliumNamespace, Name: testDefaultLoadbalancingServiceName}, &sharedCEC) 149 require.Error(t, err, "Empty CiliumEnvoyConfig must be removed") 150 require.True(t, k8sApiErrors.IsNotFound(err)) 151 }) 152 153 t.Run("Reconcile of Ingress without specific IngressClassName won't create resources if cilium IngressClass is not the default", func(t *testing.T) { 154 fakeClient := fake.NewClientBuilder(). 155 WithScheme(testScheme()). 156 WithObjects( 157 &networkingv1.Ingress{ 158 ObjectMeta: metav1.ObjectMeta{ 159 Namespace: "test", 160 Name: "test", 161 }, 162 Spec: networkingv1.IngressSpec{ 163 DefaultBackend: defaultBackend(), 164 }, 165 }, 166 &networkingv1.IngressClass{ 167 ObjectMeta: metav1.ObjectMeta{ 168 Name: "cilium", 169 Annotations: map[string]string{ 170 "ingressclass.kubernetes.io/is-default-class": "false", 171 }, 172 }, 173 }, 174 ). 175 Build() 176 177 cecTranslator := translation.NewCECTranslator(testCiliumSecretsNamespace, testUseProxyProtocol, false, false, testDefaultTimeout, false, nil, false, false, 0) 178 dedicatedIngressTranslator := ingressTranslation.NewDedicatedIngressTranslator(cecTranslator, false) 179 180 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, dedicatedIngressTranslator, testCiliumNamespace, []string{}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, false, 0) 181 182 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 183 NamespacedName: types.NamespacedName{ 184 Namespace: "test", 185 Name: "test", 186 }, 187 }) 188 require.NoError(t, err) 189 require.NotNil(t, result) 190 191 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &corev1.Service{}) 192 require.True(t, k8sApiErrors.IsNotFound(err), "Service should not be created") 193 194 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &corev1.Endpoints{}) 195 require.True(t, k8sApiErrors.IsNotFound(err), "Endpoints should not be created") 196 197 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test-test"}, &ciliumv2.CiliumEnvoyConfig{}) 198 require.True(t, k8sApiErrors.IsNotFound(err), "CiliumEnvoyConfig should not be created") 199 }) 200 201 t.Run("Reconcile of shared Cilium Ingress will create the shared CiliumEnvoyConfig in the cilium namespace", func(t *testing.T) { 202 fakeClient := fake.NewClientBuilder(). 203 WithScheme(testScheme()). 204 WithObjects( 205 &networkingv1.Ingress{ 206 ObjectMeta: metav1.ObjectMeta{ 207 Namespace: "test", 208 Name: "test", 209 Annotations: map[string]string{ 210 "ingress.cilium.io/loadbalancer-mode": "shared", 211 }, 212 }, 213 Spec: networkingv1.IngressSpec{ 214 IngressClassName: model.AddressOf("cilium"), 215 DefaultBackend: defaultBackend(), 216 }, 217 }, 218 ). 219 Build() 220 221 cecTranslator := translation.NewCECTranslator(testCiliumSecretsNamespace, testUseProxyProtocol, false, false, testDefaultTimeout, false, nil, false, false, 0) 222 dedicatedIngressTranslator := ingressTranslation.NewDedicatedIngressTranslator(cecTranslator, false) 223 224 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, dedicatedIngressTranslator, testCiliumNamespace, []string{}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, false, 0) 225 226 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 227 NamespacedName: types.NamespacedName{ 228 Namespace: "test", 229 Name: "test", 230 }, 231 }) 232 require.NoError(t, err) 233 require.NotNil(t, result) 234 235 sharedCEC := ciliumv2.CiliumEnvoyConfig{} 236 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: testCiliumNamespace, Name: testDefaultLoadbalancingServiceName}, &sharedCEC) 237 require.NoError(t, err, "Shared CiliumEnvoyConfig should exist for shared Ingress") 238 require.NotEmpty(t, sharedCEC.Spec.Resources) 239 240 svc := corev1.Service{} 241 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &svc) 242 require.True(t, k8sApiErrors.IsNotFound(err), "Dedicated loadbalancer service should not exist for shared Ingress") 243 244 ep := corev1.Endpoints{} 245 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &ep) 246 require.True(t, k8sApiErrors.IsNotFound(err), "Dedicated loadbalancer endpoints should not exist for shared Ingress") 247 248 cec := ciliumv2.CiliumEnvoyConfig{} 249 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test-test"}, &cec) 250 require.True(t, k8sApiErrors.IsNotFound(err), "Dedicated CiliumEnvoyConfig should not exist for shared Ingress") 251 }) 252 253 t.Run("Reconcile of Cilium Ingress will cleanup any potentially existing resources of the other loadbalancing mode (changing from dedicated -> shared)", func(t *testing.T) { 254 fakeClient := fake.NewClientBuilder(). 255 WithScheme(testScheme()). 256 WithObjects( 257 &networkingv1.Ingress{ 258 ObjectMeta: metav1.ObjectMeta{ 259 Namespace: "test", 260 Name: "test", 261 Annotations: map[string]string{ 262 "ingress.cilium.io/loadbalancer-mode": "shared", 263 }, 264 }, 265 Spec: networkingv1.IngressSpec{ 266 IngressClassName: model.AddressOf("cilium"), 267 DefaultBackend: defaultBackend(), 268 }, 269 }, 270 &corev1.Service{ 271 ObjectMeta: metav1.ObjectMeta{ 272 Namespace: "test", 273 Name: "cilium-ingress-test", 274 }, 275 }, 276 &corev1.Endpoints{ 277 ObjectMeta: metav1.ObjectMeta{ 278 Namespace: "test", 279 Name: "cilium-ingress-test", 280 }, 281 }, 282 &ciliumv2.CiliumEnvoyConfig{ 283 ObjectMeta: metav1.ObjectMeta{ 284 Namespace: "test", 285 Name: "cilium-ingress-test-test", 286 }, 287 }, 288 ). 289 Build() 290 291 cecTranslator := translation.NewCECTranslator(testCiliumSecretsNamespace, testUseProxyProtocol, false, false, testDefaultTimeout, false, nil, false, false, 0) 292 dedicatedIngressTranslator := ingressTranslation.NewDedicatedIngressTranslator(cecTranslator, false) 293 294 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, dedicatedIngressTranslator, testCiliumNamespace, []string{}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, false, 0) 295 296 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 297 NamespacedName: types.NamespacedName{ 298 Namespace: "test", 299 Name: "test", 300 }, 301 }) 302 require.NoError(t, err) 303 require.NotNil(t, result) 304 305 sharedCEC := ciliumv2.CiliumEnvoyConfig{} 306 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: testCiliumNamespace, Name: testDefaultLoadbalancingServiceName}, &sharedCEC) 307 require.NoError(t, err, "Shared CiliumEnvoyConfig should exist for shared Ingress") 308 require.NotEmpty(t, sharedCEC.Spec.Resources) 309 310 svc := corev1.Service{} 311 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &svc) 312 require.True(t, k8sApiErrors.IsNotFound(err), "Dedicated loadbalancer service should not exist for shared Ingress") 313 314 ep := corev1.Endpoints{} 315 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &ep) 316 require.True(t, k8sApiErrors.IsNotFound(err), "Dedicated loadbalancer endpoints should not exist for shared Ingress") 317 318 cec := ciliumv2.CiliumEnvoyConfig{} 319 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test-test"}, &cec) 320 require.True(t, k8sApiErrors.IsNotFound(err), "Dedicated CiliumEnvoyConfig should not exist for shared Ingress") 321 }) 322 323 t.Run("Reconcile of Cilium Ingress will cleanup any potentially existing resources of the other loadbalancing mode (changing from shared -> dedicated)", func(t *testing.T) { 324 fakeClient := fake.NewClientBuilder(). 325 WithScheme(testScheme()). 326 WithObjects( 327 &networkingv1.Ingress{ 328 ObjectMeta: metav1.ObjectMeta{ 329 Namespace: "test", 330 Name: "test", 331 Annotations: map[string]string{ 332 "ingress.cilium.io/loadbalancer-mode": "dedicated", 333 }, 334 }, 335 Spec: networkingv1.IngressSpec{ 336 IngressClassName: model.AddressOf("cilium"), 337 DefaultBackend: defaultBackend(), 338 }, 339 }, 340 &ciliumv2.CiliumEnvoyConfig{ 341 ObjectMeta: metav1.ObjectMeta{ 342 Namespace: testCiliumNamespace, 343 Name: testDefaultLoadbalancingServiceName, 344 }, 345 Spec: ciliumv2.CiliumEnvoyConfigSpec{ 346 Resources: []ciliumv2.XDSResource{ 347 { 348 Any: &anypb.Any{ 349 TypeUrl: envoy.ListenerTypeURL, 350 }, 351 }, 352 }, 353 }, 354 }, 355 ). 356 Build() 357 358 cecTranslator := translation.NewCECTranslator(testCiliumSecretsNamespace, testUseProxyProtocol, false, false, testDefaultTimeout, false, nil, false, false, 0) 359 dedicatedIngressTranslator := ingressTranslation.NewDedicatedIngressTranslator(cecTranslator, false) 360 361 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, dedicatedIngressTranslator, testCiliumNamespace, []string{}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, false, 0) 362 363 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 364 NamespacedName: types.NamespacedName{ 365 Namespace: "test", 366 Name: "test", 367 }, 368 }) 369 require.NoError(t, err) 370 require.NotNil(t, result) 371 372 svc := corev1.Service{} 373 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &svc) 374 require.NoError(t, err, "Dedicated loadbalancer service should exist") 375 376 ep := corev1.Endpoints{} 377 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &ep) 378 require.NoError(t, err, "Dedicated loadbalancer service endpoints should exist") 379 380 cec := ciliumv2.CiliumEnvoyConfig{} 381 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test-test"}, &cec) 382 require.NoError(t, err, "Dedicated CiliumEnvoyConfig should exist") 383 384 sharedCEC := ciliumv2.CiliumEnvoyConfig{} 385 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: testCiliumNamespace, Name: testDefaultLoadbalancingServiceName}, &sharedCEC) 386 require.Error(t, err, "Empty CiliumEnvoyConfig must be removed") 387 require.True(t, k8sApiErrors.IsNotFound(err)) 388 }) 389 390 t.Run("Reconcile of a non-existent, potentially deleted, Cilium Ingress will try to cleanup any potentially existing shared resources", func(t *testing.T) { 391 fakeClient := fake.NewClientBuilder(). 392 WithScheme(testScheme()). 393 WithObjects( 394 &ciliumv2.CiliumEnvoyConfig{ 395 ObjectMeta: metav1.ObjectMeta{ 396 Namespace: testCiliumNamespace, 397 Name: testDefaultLoadbalancingServiceName, 398 }, 399 Spec: ciliumv2.CiliumEnvoyConfigSpec{ 400 Resources: []ciliumv2.XDSResource{ 401 { 402 Any: &anypb.Any{ 403 TypeUrl: envoy.ListenerTypeURL, 404 }, 405 }, 406 }, 407 }, 408 }, 409 ). 410 Build() 411 412 cecTranslator := translation.NewCECTranslator(testCiliumSecretsNamespace, testUseProxyProtocol, false, false, testDefaultTimeout, false, nil, false, false, 0) 413 dedicatedIngressTranslator := ingressTranslation.NewDedicatedIngressTranslator(cecTranslator, false) 414 415 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, dedicatedIngressTranslator, testCiliumNamespace, []string{}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, false, 0) 416 417 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 418 NamespacedName: types.NamespacedName{ 419 Namespace: "test", 420 Name: "test", 421 }, 422 }) 423 require.NoError(t, err) 424 require.NotNil(t, result) 425 426 sharedCEC := ciliumv2.CiliumEnvoyConfig{} 427 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: testCiliumNamespace, Name: testDefaultLoadbalancingServiceName}, &sharedCEC) 428 require.Error(t, err, "Empty CiliumEnvoyConfig must be removed") 429 require.True(t, k8sApiErrors.IsNotFound(err)) 430 }) 431 432 t.Run("Reconcile of non Cilium Ingress will cleanup any potentially existing resources (dedicated and shared) and reset the Ingress status", func(t *testing.T) { 433 fakeClient := fake.NewClientBuilder(). 434 WithScheme(testScheme()). 435 WithObjects( 436 &networkingv1.Ingress{ 437 ObjectMeta: metav1.ObjectMeta{ 438 Namespace: "test", 439 Name: "test", 440 Annotations: map[string]string{ 441 "ingress.cilium.io/loadbalancer-mode": "dedicated", 442 }, 443 }, 444 Spec: networkingv1.IngressSpec{ 445 IngressClassName: model.AddressOf("other"), 446 DefaultBackend: defaultBackend(), 447 }, 448 Status: networkingv1.IngressStatus{ 449 LoadBalancer: networkingv1.IngressLoadBalancerStatus{ 450 Ingress: []networkingv1.IngressLoadBalancerIngress{ 451 { 452 IP: "172.21.255.202", 453 }, 454 }, 455 }, 456 }, 457 }, 458 &corev1.Service{ 459 ObjectMeta: metav1.ObjectMeta{ 460 Namespace: "test", 461 Name: "cilium-ingress-test", 462 }, 463 }, 464 &corev1.Endpoints{ 465 ObjectMeta: metav1.ObjectMeta{ 466 Namespace: "test", 467 Name: "cilium-ingress-test", 468 }, 469 }, 470 &ciliumv2.CiliumEnvoyConfig{ 471 ObjectMeta: metav1.ObjectMeta{ 472 Namespace: "test", 473 Name: "cilium-ingress-test-test", 474 }, 475 }, 476 &ciliumv2.CiliumEnvoyConfig{ 477 ObjectMeta: metav1.ObjectMeta{ 478 Namespace: testCiliumNamespace, 479 Name: testDefaultLoadbalancingServiceName, 480 }, 481 Spec: ciliumv2.CiliumEnvoyConfigSpec{ 482 Resources: []ciliumv2.XDSResource{ 483 { 484 Any: &anypb.Any{ 485 TypeUrl: envoy.ListenerTypeURL, 486 }, 487 }, 488 }, 489 }, 490 }, 491 ). 492 Build() 493 494 cecTranslator := translation.NewCECTranslator(testCiliumSecretsNamespace, testUseProxyProtocol, false, false, testDefaultTimeout, false, nil, false, false, 0) 495 dedicatedIngressTranslator := ingressTranslation.NewDedicatedIngressTranslator(cecTranslator, false) 496 497 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, dedicatedIngressTranslator, testCiliumNamespace, []string{}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, false, 0) 498 499 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 500 NamespacedName: types.NamespacedName{ 501 Namespace: "test", 502 Name: "test", 503 }, 504 }) 505 require.NoError(t, err) 506 require.NotNil(t, result) 507 508 svc := corev1.Service{} 509 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &svc) 510 require.True(t, k8sApiErrors.IsNotFound(err), "Dedicated loadbalancer service should be cleaned up") 511 512 ep := corev1.Endpoints{} 513 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &ep) 514 require.True(t, k8sApiErrors.IsNotFound(err), "Dedicated loadbalancer endpoints should be cleaned up") 515 516 cec := ciliumv2.CiliumEnvoyConfig{} 517 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test-test"}, &cec) 518 require.True(t, k8sApiErrors.IsNotFound(err), "Dedicated CiliumEnvoyConfig should be cleaned up") 519 520 sharedCEC := ciliumv2.CiliumEnvoyConfig{} 521 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: testCiliumNamespace, Name: testDefaultLoadbalancingServiceName}, &sharedCEC) 522 require.Error(t, err, "Empty CiliumEnvoyConfig must be removed") 523 require.True(t, k8sApiErrors.IsNotFound(err)) 524 525 ingress := networkingv1.Ingress{} 526 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "test"}, &ingress) 527 require.NoError(t, err) 528 require.Empty(t, ingress.Status.LoadBalancer.Ingress, "Loadbalancer status of Ingress should be reset") 529 }) 530 531 t.Run("Reconcile of dedicated Cilium Ingress with loadbalancer class will create the dedicated loadbalancer service with the specified class", func(t *testing.T) { 532 fakeClient := fake.NewClientBuilder(). 533 WithScheme(testScheme()). 534 WithObjects( 535 &networkingv1.Ingress{ 536 ObjectMeta: metav1.ObjectMeta{ 537 Namespace: "test", 538 Name: "test", 539 Annotations: map[string]string{ 540 "ingress.cilium.io/loadbalancer-mode": "dedicated", 541 "ingress.cilium.io/loadbalancer-class": "dummy", 542 }, 543 }, 544 Spec: networkingv1.IngressSpec{ 545 IngressClassName: model.AddressOf("cilium"), 546 DefaultBackend: defaultBackend(), 547 }, 548 }, 549 ). 550 Build() 551 552 cecTranslator := translation.NewCECTranslator(testCiliumSecretsNamespace, testUseProxyProtocol, false, false, testDefaultTimeout, false, nil, false, false, 0) 553 dedicatedIngressTranslator := ingressTranslation.NewDedicatedIngressTranslator(cecTranslator, false) 554 555 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, dedicatedIngressTranslator, testCiliumNamespace, []string{}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, false, 0) 556 557 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 558 NamespacedName: types.NamespacedName{ 559 Namespace: "test", 560 Name: "test", 561 }, 562 }) 563 require.NoError(t, err) 564 require.NotNil(t, result) 565 566 svc := corev1.Service{} 567 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &svc) 568 require.NoError(t, err, "Dedicated loadbalancer service should exist") 569 require.Equal(t, "dummy", *svc.Spec.LoadBalancerClass, "Dedicated loadbalancer service should haver the specified class") 570 }) 571 572 t.Run("Reconcile of shared Cilium Ingress with loadbalancer class will not create a dedicated load balancer", func(t *testing.T) { 573 fakeClient := fake.NewClientBuilder(). 574 WithScheme(testScheme()). 575 WithObjects( 576 &networkingv1.Ingress{ 577 ObjectMeta: metav1.ObjectMeta{ 578 Namespace: "test", 579 Name: "test", 580 Annotations: map[string]string{ 581 "ingress.cilium.io/loadbalancer-mode": "shared", 582 "ingress.cilium.io/loadbalancer-class": "dummy", 583 }, 584 }, 585 Spec: networkingv1.IngressSpec{ 586 IngressClassName: model.AddressOf("cilium"), 587 DefaultBackend: defaultBackend(), 588 }, 589 }, 590 ). 591 Build() 592 593 cecTranslator := translation.NewCECTranslator(testCiliumSecretsNamespace, testUseProxyProtocol, false, false, testDefaultTimeout, false, nil, false, false, 0) 594 dedicatedIngressTranslator := ingressTranslation.NewDedicatedIngressTranslator(cecTranslator, false) 595 596 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, dedicatedIngressTranslator, testCiliumNamespace, []string{}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, false, 0) 597 598 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 599 NamespacedName: types.NamespacedName{ 600 Namespace: "test", 601 Name: "test", 602 }, 603 }) 604 require.NoError(t, err) 605 require.NotNil(t, result) 606 607 sharedCEC := ciliumv2.CiliumEnvoyConfig{} 608 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: testCiliumNamespace, Name: testDefaultLoadbalancingServiceName}, &sharedCEC) 609 require.NoError(t, err, "Shared CiliumEnvoyConfig should exist for shared Ingress") 610 require.NotEmpty(t, sharedCEC.Spec.Resources) 611 612 svc := corev1.Service{} 613 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &svc) 614 require.True(t, k8sApiErrors.IsNotFound(err), "Dedicated loadbalancer service should not exist for shared Ingress") 615 }) 616 617 t.Run("Reconcile of dedicated Cilium Ingress will update the status according to the IP of the dedicated loadbalancer service", func(t *testing.T) { 618 fakeClient := fake.NewClientBuilder(). 619 WithScheme(testScheme()). 620 WithObjects( 621 &networkingv1.Ingress{ 622 ObjectMeta: metav1.ObjectMeta{ 623 Namespace: "test", 624 Name: "test", 625 Annotations: map[string]string{ 626 "ingress.cilium.io/loadbalancer-mode": "dedicated", 627 }, 628 }, 629 Spec: networkingv1.IngressSpec{ 630 IngressClassName: model.AddressOf("cilium"), 631 DefaultBackend: defaultBackend(), 632 }, 633 }, 634 &corev1.Service{ 635 ObjectMeta: metav1.ObjectMeta{ 636 Namespace: "test", 637 Name: "cilium-ingress-test", 638 }, 639 Status: corev1.ServiceStatus{ 640 LoadBalancer: corev1.LoadBalancerStatus{ 641 Ingress: []corev1.LoadBalancerIngress{ 642 { 643 IP: "172.21.255.202", 644 }, 645 }, 646 }, 647 }, 648 }, 649 ). 650 Build() 651 652 cecTranslator := translation.NewCECTranslator(testCiliumSecretsNamespace, testUseProxyProtocol, false, false, testDefaultTimeout, false, nil, false, false, 0) 653 dedicatedIngressTranslator := ingressTranslation.NewDedicatedIngressTranslator(cecTranslator, false) 654 655 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, dedicatedIngressTranslator, testCiliumNamespace, []string{}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, false, 0) 656 657 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 658 NamespacedName: types.NamespacedName{ 659 Namespace: "test", 660 Name: "test", 661 }, 662 }) 663 require.NoError(t, err) 664 require.NotNil(t, result) 665 666 ingress := networkingv1.Ingress{} 667 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "test"}, &ingress) 668 require.NoError(t, err) 669 require.Len(t, ingress.Status.LoadBalancer.Ingress, 1, "Loadbalancer status should contain the IP of the dedicated loadbalancer service") 670 require.Equal(t, "172.21.255.202", ingress.Status.LoadBalancer.Ingress[0].IP, "Loadbalancer status should contain the IP of the dedicated loadbalancer service") 671 }) 672 673 t.Run("Reconcile of shared Cilium Ingress will update the status according to the IP of the shared loadbalancer service", func(t *testing.T) { 674 fakeClient := fake.NewClientBuilder(). 675 WithScheme(testScheme()). 676 WithObjects( 677 &networkingv1.Ingress{ 678 ObjectMeta: metav1.ObjectMeta{ 679 Namespace: "test", 680 Name: "test", 681 Annotations: map[string]string{ 682 "ingress.cilium.io/loadbalancer-mode": "shared", 683 }, 684 }, 685 Spec: networkingv1.IngressSpec{ 686 IngressClassName: model.AddressOf("cilium"), 687 DefaultBackend: defaultBackend(), 688 }, 689 }, 690 &corev1.Service{ 691 ObjectMeta: metav1.ObjectMeta{ 692 Namespace: testCiliumNamespace, 693 Name: "cilium-ingress", 694 }, 695 Status: corev1.ServiceStatus{ 696 LoadBalancer: corev1.LoadBalancerStatus{ 697 Ingress: []corev1.LoadBalancerIngress{ 698 { 699 IP: "172.21.255.200", 700 }, 701 }, 702 }, 703 }, 704 }, 705 ). 706 Build() 707 708 cecTranslator := translation.NewCECTranslator(testCiliumSecretsNamespace, testUseProxyProtocol, false, false, testDefaultTimeout, false, nil, false, false, 0) 709 dedicatedIngressTranslator := ingressTranslation.NewDedicatedIngressTranslator(cecTranslator, false) 710 711 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, dedicatedIngressTranslator, testCiliumNamespace, []string{}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, false, 0) 712 713 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 714 NamespacedName: types.NamespacedName{ 715 Namespace: "test", 716 Name: "test", 717 }, 718 }) 719 require.NoError(t, err) 720 require.NotNil(t, result) 721 722 ingress := networkingv1.Ingress{} 723 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "test"}, &ingress) 724 require.NoError(t, err) 725 require.Len(t, ingress.Status.LoadBalancer.Ingress, 1, "Loadbalancer status should contain the IP of the shared loadbalancer service") 726 require.Equal(t, "172.21.255.200", ingress.Status.LoadBalancer.Ingress[0].IP, "Loadbalancer status should contain the IP of the shared loadbalancer service") 727 }) 728 729 t.Run("Errors during the model translation are reported via error and result in re-enqueuing the reconcile request", func(t *testing.T) { 730 fakeClient := fake.NewClientBuilder(). 731 WithScheme(testScheme()). 732 WithObjects( 733 &networkingv1.Ingress{ 734 ObjectMeta: metav1.ObjectMeta{ 735 Namespace: "test", 736 Name: "test", 737 Annotations: map[string]string{ 738 "ingress.cilium.io/loadbalancer-mode": "dedicated", 739 }, 740 }, 741 Spec: networkingv1.IngressSpec{ 742 IngressClassName: model.AddressOf("cilium"), 743 }, 744 }, 745 ). 746 Build() 747 748 cecTranslator := translation.NewCECTranslator(testCiliumSecretsNamespace, testUseProxyProtocol, false, false, testDefaultTimeout, false, nil, false, false, 0) 749 dedicatedIngressTranslator := ingressTranslation.NewDedicatedIngressTranslator(cecTranslator, false) 750 751 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, dedicatedIngressTranslator, testCiliumNamespace, []string{}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, false, 0) 752 753 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 754 NamespacedName: types.NamespacedName{ 755 Namespace: "test", 756 Name: "test", 757 }, 758 }) 759 require.ErrorContains(t, err, "model source can't be empty") 760 require.NotNil(t, result) 761 }) 762 763 t.Run("Annotations and labels from the Ingress resource should be propagated to the Service if they match the configured prefixes", func(t *testing.T) { 764 fakeClient := fake.NewClientBuilder(). 765 WithScheme(testScheme()). 766 WithObjects( 767 &networkingv1.Ingress{ 768 ObjectMeta: metav1.ObjectMeta{ 769 Namespace: "test", 770 Name: "test", 771 Annotations: map[string]string{ 772 "ingress.cilium.io/loadbalancer-mode": "dedicated", 773 "test.acme.io/test-annotation": "test", 774 "other.acme.io/test": "test", 775 }, 776 Labels: map[string]string{ 777 "test.acme.io/test-label": "test", 778 "other.acme.io/test": "test", 779 }, 780 }, 781 Spec: networkingv1.IngressSpec{ 782 IngressClassName: model.AddressOf("cilium"), 783 DefaultBackend: defaultBackend(), 784 }, 785 }, 786 ). 787 Build() 788 789 cecTranslator := translation.NewCECTranslator(testCiliumSecretsNamespace, testUseProxyProtocol, false, false, testDefaultTimeout, false, nil, false, false, 0) 790 dedicatedIngressTranslator := ingressTranslation.NewDedicatedIngressTranslator(cecTranslator, false) 791 792 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, dedicatedIngressTranslator, testCiliumNamespace, []string{"test.acme.io/"}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, false, 0) 793 794 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 795 NamespacedName: types.NamespacedName{ 796 Namespace: "test", 797 Name: "test", 798 }, 799 }) 800 require.NoError(t, err) 801 require.NotNil(t, result) 802 803 svc := corev1.Service{} 804 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &svc) 805 require.NoError(t, err) 806 807 require.Equal(t, map[string]string{"cilium.io/ingress": "true", "test.acme.io/test-label": "test"}, svc.Labels) 808 require.Equal(t, map[string]string{"test.acme.io/test-annotation": "test"}, svc.Annotations) 809 }) 810 811 t.Run("Additional existing annotations and labels on the Service, Endpoints & CEC should be preserved", func(t *testing.T) { 812 fakeClient := fake.NewClientBuilder(). 813 WithScheme(testScheme()). 814 WithObjects( 815 &networkingv1.Ingress{ 816 ObjectMeta: metav1.ObjectMeta{ 817 Namespace: "test", 818 Name: "test", 819 Annotations: map[string]string{ 820 "ingress.cilium.io/loadbalancer-mode": "dedicated", 821 "test.acme.io/test-annotation": "test", 822 "other.acme.io/test-annotation": "test", 823 }, 824 Labels: map[string]string{ 825 "test.acme.io/test-label": "test", 826 "other.acme.io/test-label": "test", 827 }, 828 }, 829 Spec: networkingv1.IngressSpec{ 830 IngressClassName: model.AddressOf("cilium"), 831 DefaultBackend: defaultBackend(), 832 }, 833 }, 834 &corev1.Service{ 835 ObjectMeta: metav1.ObjectMeta{ 836 Namespace: "test", 837 Name: "cilium-ingress-test", 838 Annotations: map[string]string{ 839 "additional.annotation/test-annotation": "test", 840 }, 841 Labels: map[string]string{ 842 "cilium.io/ingress": "false", 843 "additional.label/test-label": "test", 844 }, 845 }, 846 }, 847 &corev1.Endpoints{ 848 ObjectMeta: metav1.ObjectMeta{ 849 Namespace: "test", 850 Name: "cilium-ingress-test", 851 Annotations: map[string]string{ 852 "additional.annotation/test-annotation": "test", 853 }, 854 Labels: map[string]string{ 855 "additional.label/test-label": "test", 856 }, 857 }, 858 }, 859 &ciliumv2.CiliumEnvoyConfig{ 860 ObjectMeta: metav1.ObjectMeta{ 861 Namespace: "test", 862 Name: "cilium-ingress-test-test", 863 Annotations: map[string]string{ 864 "additional.annotation/test-annotation": "test", 865 }, 866 Labels: map[string]string{ 867 "additional.label/test-label": "test", 868 }, 869 }, 870 }, 871 ). 872 Build() 873 874 cecTranslator := translation.NewCECTranslator(testCiliumSecretsNamespace, testUseProxyProtocol, false, false, testDefaultTimeout, false, nil, false, false, 0) 875 dedicatedIngressTranslator := ingressTranslation.NewDedicatedIngressTranslator(cecTranslator, false) 876 877 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, dedicatedIngressTranslator, testCiliumNamespace, []string{}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, false, 0) 878 879 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 880 NamespacedName: types.NamespacedName{ 881 Namespace: "test", 882 Name: "test", 883 }, 884 }) 885 require.NoError(t, err) 886 require.NotNil(t, result) 887 888 svc := corev1.Service{} 889 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &svc) 890 require.NoError(t, err) 891 892 require.Contains(t, svc.Labels, "cilium.io/ingress", "Existing labels should be overwritten if they have the same key") 893 require.Equal(t, "true", svc.Labels["cilium.io/ingress"], "Existing label should be overwritten if they have the same key") 894 895 require.Contains(t, svc.Labels, "additional.label/test-label", "Existing labels should not be deleted") 896 require.Contains(t, svc.Annotations, "additional.annotation/test-annotation", "Existing annotations should not be deleted") 897 898 ep := corev1.Endpoints{} 899 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &ep) 900 require.NoError(t, err) 901 902 require.Contains(t, ep.Labels, "additional.label/test-label", "Existing labels should not be deleted") 903 require.Contains(t, ep.Annotations, "additional.annotation/test-annotation", "Existing annotations should not be deleted") 904 905 cec := ciliumv2.CiliumEnvoyConfig{} 906 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test-test"}, &cec) 907 require.NoError(t, err) 908 909 require.Contains(t, cec.Labels, "additional.label/test-label", "Existing labels should not be deleted") 910 require.Contains(t, cec.Annotations, "additional.annotation/test-annotation", "Existing annotations should not be deleted") 911 }) 912 913 t.Run("Existing loadBalancerClass on Service should not be overwritten (e.g. scenarios where this gets set by a mutating webhook)", func(t *testing.T) { 914 fakeClient := fake.NewClientBuilder(). 915 WithScheme(testScheme()). 916 WithObjects( 917 &networkingv1.Ingress{ 918 ObjectMeta: metav1.ObjectMeta{ 919 Namespace: "test", 920 Name: "test", 921 }, 922 Spec: networkingv1.IngressSpec{ 923 IngressClassName: model.AddressOf("cilium"), 924 DefaultBackend: defaultBackend(), 925 }, 926 }, 927 &corev1.Service{ 928 ObjectMeta: metav1.ObjectMeta{ 929 Namespace: "test", 930 Name: "cilium-ingress-test", 931 }, 932 Spec: corev1.ServiceSpec{ 933 LoadBalancerClass: model.AddressOf("service.k8s.aws/nlb"), 934 }, 935 }, 936 ). 937 Build() 938 939 cecTranslator := translation.NewCECTranslator(testCiliumSecretsNamespace, testUseProxyProtocol, false, false, testDefaultTimeout, false, nil, false, false, 0) 940 dedicatedIngressTranslator := ingressTranslation.NewDedicatedIngressTranslator(cecTranslator, false) 941 942 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, dedicatedIngressTranslator, testCiliumNamespace, []string{}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, false, 0) 943 944 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 945 NamespacedName: types.NamespacedName{ 946 Namespace: "test", 947 Name: "test", 948 }, 949 }) 950 require.NoError(t, err) 951 require.NotNil(t, result) 952 953 svc := corev1.Service{} 954 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &svc) 955 require.NoError(t, err) 956 957 require.Equal(t, model.AddressOf("service.k8s.aws/nlb"), svc.Spec.LoadBalancerClass, "LoadbalancerClass should be preserved during reconciliation") 958 }) 959 960 t.Run("If the deletionTimestamp is set (foreground deletion), no dependent objects should be modified or created", func(t *testing.T) { 961 fakeClient := fake.NewClientBuilder(). 962 WithScheme(testScheme()). 963 WithObjects( 964 &networkingv1.Ingress{ 965 ObjectMeta: metav1.ObjectMeta{ 966 Namespace: "test", 967 Name: "test", 968 DeletionTimestamp: model.AddressOf(metav1.Now()), 969 Finalizers: []string{ 970 "foregroundDeletion", 971 }, 972 }, 973 Spec: networkingv1.IngressSpec{ 974 IngressClassName: model.AddressOf("cilium"), 975 DefaultBackend: defaultBackend(), 976 }, 977 }, 978 ). 979 Build() 980 981 cecTranslator := translation.NewCECTranslator(testCiliumSecretsNamespace, testUseProxyProtocol, false, false, testDefaultTimeout, false, nil, false, false, 0) 982 dedicatedIngressTranslator := ingressTranslation.NewDedicatedIngressTranslator(cecTranslator, false) 983 984 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, dedicatedIngressTranslator, testCiliumNamespace, []string{}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, false, 0) 985 986 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 987 NamespacedName: types.NamespacedName{ 988 Namespace: "test", 989 Name: "test", 990 }, 991 }) 992 require.NoError(t, err) 993 require.NotNil(t, result) 994 995 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &corev1.Service{}) 996 require.True(t, k8sApiErrors.IsNotFound(err), "Service should not be created") 997 998 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &corev1.Endpoints{}) 999 require.True(t, k8sApiErrors.IsNotFound(err), "Endpoints should not be created") 1000 1001 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test-test"}, &ciliumv2.CiliumEnvoyConfig{}) 1002 require.True(t, k8sApiErrors.IsNotFound(err), "CiliumEnvoyConfig should not be created") 1003 }) 1004 1005 t.Run("If create operations fail due to namespace termination, no error should be reported", func(t *testing.T) { 1006 fakeClient := fake.NewClientBuilder(). 1007 WithScheme(testScheme()). 1008 WithObjects( 1009 &networkingv1.Ingress{ 1010 ObjectMeta: metav1.ObjectMeta{ 1011 Namespace: "test", 1012 Name: "test", 1013 }, 1014 Spec: networkingv1.IngressSpec{ 1015 IngressClassName: model.AddressOf("cilium"), 1016 DefaultBackend: defaultBackend(), 1017 }, 1018 }, 1019 ). 1020 WithInterceptorFuncs(interceptor.Funcs{ 1021 Create: func(ctx context.Context, client client.WithWatch, obj client.Object, opts ...client.CreateOption) error { 1022 return &k8sApiErrors.StatusError{ 1023 ErrStatus: metav1.Status{ 1024 Message: "unable to create new content in namespace test because it is being terminated", 1025 Reason: metav1.StatusReasonForbidden, 1026 Details: &metav1.StatusDetails{ 1027 Causes: []metav1.StatusCause{ 1028 { 1029 Type: corev1.NamespaceTerminatingCause, 1030 }, 1031 }, 1032 }, 1033 }, 1034 } 1035 }, 1036 }). 1037 Build() 1038 1039 cecTranslator := translation.NewCECTranslator(testCiliumSecretsNamespace, testUseProxyProtocol, false, false, testDefaultTimeout, false, nil, false, false, 0) 1040 dedicatedIngressTranslator := ingressTranslation.NewDedicatedIngressTranslator(cecTranslator, false) 1041 1042 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, dedicatedIngressTranslator, testCiliumNamespace, []string{}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, false, 0) 1043 1044 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 1045 NamespacedName: types.NamespacedName{ 1046 Namespace: "test", 1047 Name: "test", 1048 }, 1049 }) 1050 require.NoError(t, err) 1051 require.NotNil(t, result) 1052 1053 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &corev1.Service{}) 1054 require.True(t, k8sApiErrors.IsNotFound(err), "Service should not be created") 1055 1056 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test"}, &corev1.Endpoints{}) 1057 require.True(t, k8sApiErrors.IsNotFound(err), "Endpoints should not be created") 1058 1059 err = fakeClient.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "cilium-ingress-test-test"}, &ciliumv2.CiliumEnvoyConfig{}) 1060 require.True(t, k8sApiErrors.IsNotFound(err), "CiliumEnvoyConfig should not be created") 1061 }) 1062 t.Run("Reconcile of shared Cilium Ingress with external LB support will pass the configured port via model to the CEC Translator", func(t *testing.T) { 1063 fakeClient := fake.NewClientBuilder(). 1064 WithScheme(testScheme()). 1065 WithObjects( 1066 &networkingv1.Ingress{ 1067 ObjectMeta: metav1.ObjectMeta{ 1068 Namespace: "test", 1069 Name: "test", 1070 Annotations: map[string]string{ 1071 "ingress.cilium.io/loadbalancer-mode": "shared", 1072 }, 1073 }, 1074 Spec: networkingv1.IngressSpec{ 1075 IngressClassName: model.AddressOf("cilium"), 1076 DefaultBackend: defaultBackend(), 1077 }, 1078 }, 1079 ). 1080 Build() 1081 1082 cecTranslator := &fakeCECTranslator{} 1083 1084 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, nil, testCiliumNamespace, []string{}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, true, 55555) 1085 1086 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 1087 NamespacedName: types.NamespacedName{ 1088 Namespace: "test", 1089 Name: "test", 1090 }, 1091 }) 1092 require.NoError(t, err) 1093 require.NotNil(t, result) 1094 1095 assert.Empty(t, cecTranslator.model.TLSPassthrough) 1096 assert.Len(t, cecTranslator.model.HTTP, 1) 1097 assert.Equal(t, uint32(55555), cecTranslator.model.HTTP[0].Port) 1098 }) 1099 1100 t.Run("Reconcile of dedicated Cilium Ingress with external LB support will pass the annotated port to the CEC Translator", func(t *testing.T) { 1101 fakeClient := fake.NewClientBuilder(). 1102 WithScheme(testScheme()). 1103 WithObjects( 1104 &networkingv1.Ingress{ 1105 ObjectMeta: metav1.ObjectMeta{ 1106 Namespace: "test", 1107 Name: "test", 1108 Annotations: map[string]string{ 1109 "ingress.cilium.io/loadbalancer-mode": "dedicated", 1110 "ingress.cilium.io/host-listener-port": "55555", 1111 }, 1112 }, 1113 Spec: networkingv1.IngressSpec{ 1114 IngressClassName: model.AddressOf("cilium"), 1115 DefaultBackend: defaultBackend(), 1116 }, 1117 }, 1118 ). 1119 Build() 1120 1121 cecTranslator := &fakeCECTranslator{} 1122 dedicatedIngressTranslator := &fakeDedicatedIngressTranslator{} 1123 1124 reconciler := newIngressReconciler(logger, fakeClient, cecTranslator, dedicatedIngressTranslator, testCiliumNamespace, []string{}, testDefaultLoadbalancingServiceName, "dedicated", testDefaultSecretNamespace, testDefaultSecretName, false, testIngressDefaultRequestTimeout, true, 0) 1125 1126 result, err := reconciler.Reconcile(context.Background(), reconcile.Request{ 1127 NamespacedName: types.NamespacedName{ 1128 Namespace: "test", 1129 Name: "test", 1130 }, 1131 }) 1132 require.NoError(t, err) 1133 require.NotNil(t, result) 1134 1135 assert.Empty(t, dedicatedIngressTranslator.model.TLSPassthrough) 1136 assert.Len(t, dedicatedIngressTranslator.model.HTTP, 1) 1137 assert.Equal(t, uint32(55555), dedicatedIngressTranslator.model.HTTP[0].Port) 1138 }) 1139 } 1140 1141 var _ translation.CECTranslator = &fakeCECTranslator{} 1142 1143 type fakeCECTranslator struct { 1144 model *model.Model 1145 } 1146 1147 func (r *fakeCECTranslator) WithUseAlpn(useAlpn bool) { 1148 } 1149 1150 func (r *fakeCECTranslator) Translate(namespace string, name string, model *model.Model) (*ciliumv2.CiliumEnvoyConfig, error) { 1151 r.model = model 1152 1153 return &ciliumv2.CiliumEnvoyConfig{ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "test"}}, nil 1154 } 1155 1156 var _ translation.Translator = &fakeDedicatedIngressTranslator{} 1157 1158 type fakeDedicatedIngressTranslator struct { 1159 model *model.Model 1160 } 1161 1162 func (r *fakeDedicatedIngressTranslator) Translate(model *model.Model) (*ciliumv2.CiliumEnvoyConfig, *corev1.Service, *corev1.Endpoints, error) { 1163 r.model = model 1164 1165 return &ciliumv2.CiliumEnvoyConfig{ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "test"}}, 1166 &corev1.Service{ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "test"}}, 1167 &corev1.Endpoints{ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "test"}}, 1168 nil 1169 } 1170 1171 func defaultBackend() *networkingv1.IngressBackend { 1172 return &networkingv1.IngressBackend{ 1173 Service: &networkingv1.IngressServiceBackend{ 1174 Name: "test", 1175 Port: networkingv1.ServiceBackendPort{ 1176 Number: 8080, 1177 }, 1178 }, 1179 } 1180 } 1181 1182 func TestGetSharedListenerPorts(t *testing.T) { 1183 testCases := []struct { 1184 desc string 1185 hostNetworkEnabled bool 1186 hostNetworkSharedPort uint32 1187 expectedPassthroughPort uint32 1188 expectedInsecureHTTPPort uint32 1189 expectedSecureHTTPPort uint32 1190 }{ 1191 { 1192 desc: "no external loadbalancer", 1193 hostNetworkEnabled: false, 1194 expectedPassthroughPort: 443, 1195 expectedInsecureHTTPPort: 80, 1196 expectedSecureHTTPPort: 443, 1197 }, 1198 { 1199 desc: "external loadbalancer with port 0", 1200 hostNetworkEnabled: true, 1201 hostNetworkSharedPort: 0, 1202 expectedPassthroughPort: 8080, 1203 expectedInsecureHTTPPort: 8080, 1204 expectedSecureHTTPPort: 8080, 1205 }, 1206 { 1207 desc: "external loadbalancer with port 55555", 1208 hostNetworkEnabled: true, 1209 hostNetworkSharedPort: 55555, 1210 expectedPassthroughPort: 55555, 1211 expectedInsecureHTTPPort: 55555, 1212 expectedSecureHTTPPort: 55555, 1213 }, 1214 } 1215 for _, tC := range testCases { 1216 t.Run(tC.desc, func(t *testing.T) { 1217 ir := ingressReconciler{ 1218 hostNetworkEnabled: tC.hostNetworkEnabled, 1219 hostNetworkSharedPort: tC.hostNetworkSharedPort, 1220 } 1221 1222 passthrough, insecureHTTP, secureHTTP := ir.getSharedListenerPorts() 1223 1224 assert.Equal(t, tC.expectedPassthroughPort, passthrough) 1225 assert.Equal(t, tC.expectedInsecureHTTPPort, insecureHTTP) 1226 assert.Equal(t, tC.expectedSecureHTTPPort, secureHTTP) 1227 }) 1228 } 1229 } 1230 1231 func TestGetDedicatedListenerPorts(t *testing.T) { 1232 testCases := []struct { 1233 desc string 1234 hostNetworkEnabled bool 1235 ingressAnnotations map[string]string 1236 expectedPassthroughPort uint32 1237 expectedInsecureHTTPPort uint32 1238 expectedSecureHTTPPort uint32 1239 }{ 1240 { 1241 desc: "no hostnetwork mode", 1242 hostNetworkEnabled: false, 1243 expectedPassthroughPort: 443, 1244 expectedInsecureHTTPPort: 80, 1245 expectedSecureHTTPPort: 443, 1246 }, 1247 { 1248 desc: "hostnetwork without port annotation", 1249 hostNetworkEnabled: true, 1250 ingressAnnotations: map[string]string{ 1251 "ingress.cilium.io/host-listener-port": "55555", 1252 }, 1253 expectedPassthroughPort: 55555, 1254 expectedInsecureHTTPPort: 55555, 1255 expectedSecureHTTPPort: 55555, 1256 }, 1257 { 1258 desc: "hostnetwork with port annotation of value 0", 1259 hostNetworkEnabled: true, 1260 ingressAnnotations: map[string]string{ 1261 "ingress.cilium.io/host-listener-port": "0", 1262 }, 1263 expectedPassthroughPort: 8080, 1264 expectedInsecureHTTPPort: 8080, 1265 expectedSecureHTTPPort: 8080, 1266 }, 1267 { 1268 desc: "hostnetwork with invalid value", 1269 hostNetworkEnabled: true, 1270 ingressAnnotations: map[string]string{ 1271 "ingress.cilium.io/host-listener-port": "invalid", 1272 }, 1273 expectedPassthroughPort: 8080, 1274 expectedInsecureHTTPPort: 8080, 1275 expectedSecureHTTPPort: 8080, 1276 }, 1277 } 1278 for _, tC := range testCases { 1279 t.Run(tC.desc, func(t *testing.T) { 1280 logger := logrus.New() 1281 logger.SetOutput(io.Discard) 1282 ir := ingressReconciler{ 1283 logger: logger, 1284 hostNetworkEnabled: tC.hostNetworkEnabled, 1285 } 1286 1287 ingress := &networkingv1.Ingress{ 1288 ObjectMeta: metav1.ObjectMeta{ 1289 Annotations: tC.ingressAnnotations, 1290 }, 1291 } 1292 passthrough, insecureHTTP, secureHTTP := ir.getDedicatedListenerPorts(ingress) 1293 1294 assert.Equal(t, tC.expectedPassthroughPort, passthrough) 1295 assert.Equal(t, tC.expectedInsecureHTTPPort, insecureHTTP) 1296 assert.Equal(t, tC.expectedSecureHTTPPort, secureHTTP) 1297 }) 1298 } 1299 }