Skip to content

Commit 9ee381c

Browse files
committed
[NE-2183] Add e2e test for gateway conditions
1 parent 8af852d commit 9ee381c

File tree

1 file changed

+327
-5
lines changed

1 file changed

+327
-5
lines changed

test/e2e/gateway_api_test.go

Lines changed: 327 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package e2e
66
import (
77
"context"
88
"fmt"
9+
"math/rand/v2"
910
"strings"
1011
"testing"
1112
"time"
@@ -16,6 +17,10 @@ import (
1617
operatorclient "github.com/openshift/cluster-ingress-operator/pkg/operator/client"
1718
operatorcontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller"
1819
util "github.com/openshift/cluster-ingress-operator/pkg/util"
20+
"github.com/stretchr/testify/assert"
21+
"github.com/stretchr/testify/require"
22+
condutils "k8s.io/apimachinery/pkg/api/meta"
23+
"k8s.io/apimachinery/pkg/fields"
1924

2025
corev1 "k8s.io/api/core/v1"
2126
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -93,21 +98,22 @@ func TestGatewayAPI(t *testing.T) {
9398
// TODO: Uninstall OSSM after test is completed.
9499
})
95100

96-
t.Run("testGatewayAPIResources", testGatewayAPIResources)
101+
//t.Run("testGatewayAPIResources", testGatewayAPIResources)
97102
if gatewayAPIControllerEnabled {
98-
t.Run("testGatewayAPIObjects", testGatewayAPIObjects)
103+
/*t.Run("testGatewayAPIObjects", testGatewayAPIObjects)
99104
t.Run("testGatewayAPIManualDeployment", testGatewayAPIManualDeployment)
100105
t.Run("testGatewayAPIIstioInstallation", testGatewayAPIIstioInstallation)
101106
t.Run("testGatewayAPIDNS", testGatewayAPIDNS)
102107
t.Run("testGatewayAPIDNSListenerUpdate", testGatewayAPIDNSListenerUpdate)
103-
t.Run("testGatewayAPIDNSListenerWithNoHostname", testGatewayAPIDNSListenerWithNoHostname)
108+
t.Run("testGatewayAPIDNSListenerWithNoHostname", testGatewayAPIDNSListenerWithNoHostname)*/
109+
t.Run("testGatewayOpenshiftConditions", testGatewayOpenshiftConditions)
104110

105111
} else {
106112
t.Log("Gateway API Controller not enabled, skipping controller tests")
107113
}
108-
t.Run("testGatewayAPIResourcesProtection", testGatewayAPIResourcesProtection)
114+
/*t.Run("testGatewayAPIResourcesProtection", testGatewayAPIResourcesProtection)
109115
t.Run("testGatewayAPIRBAC", testGatewayAPIRBAC)
110-
t.Run("testOperatorDegradedCondition", testOperatorDegradedCondition)
116+
t.Run("testOperatorDegradedCondition", testOperatorDegradedCondition)*/
111117
}
112118

113119
// testGatewayAPIResources tests that Gateway API Custom Resource Definitions are available.
@@ -587,7 +593,323 @@ func testGatewayAPIDNS(t *testing.T) {
587593
}
588594
})
589595
}
596+
}
597+
598+
// This e2e test will verify the following scenarios:
599+
// 1 - Creating a gateway with the right base domain but outside of `openshift-ingress`
600+
// namespace will not generate a DNSRecord nor add conditions to the gateway
601+
// 2 - Creating a Gateway on `openshift-ingress` namespace using the wrong base
602+
// domain should add DNS conditions that there are no managed zones for this case
603+
// 3 - Creating a Gateway with the right base domain on `openshift-ingress` will
604+
// add the conditions on the gateway reflecting the right status of LoadBalancer and DNSRecord
605+
// 4 - Bumping some label on the Gateway should trigger a reconciliation that will
606+
// bump the generation of conditions
607+
// 5 - Adding a label on DNSRecord and/or Service will trigger a reconciliation
608+
// that should be verified by a newly recorded event
609+
func testGatewayOpenshiftConditions(t *testing.T) {
610+
domain := "gwcondtest." + dnsConfig.Spec.BaseDomain
611+
612+
gatewayClass, err := createGatewayClass(t, operatorcontroller.OpenShiftDefaultGatewayClassName, operatorcontroller.OpenShiftGatewayClassControllerName)
613+
require.NoError(t, err, "failed to create gatewayclass")
614+
615+
t.Run("creating a new gateway outside of 'openshift-ingress' namespace should not get openshift conditions", func(t *testing.T) {
616+
rnd := rand.IntN(1000)
617+
name := fmt.Sprintf("gw-test-%d", rnd)
618+
testDomain := fmt.Sprintf("some-%d.%s", rnd, domain)
619+
gateway, err := createGateway(gatewayClass, name, "default", testDomain)
620+
require.NoError(t, err, "failed to create gateway", "name", name)
621+
t.Cleanup(func() {
622+
require.NoError(t, client.IgnoreNotFound(kclient.Delete(context.TODO(), gateway)), "failed to clean test gateway", "name", name)
623+
})
624+
625+
gateway, err = assertGatewaySuccessful(t, "default", name)
626+
require.NoError(t, err, "failed waiting gateway to be ready")
627+
// Give some time to guarantee our controller will watch the change but ignore it
628+
time.Sleep(time.Second)
629+
630+
// Get gateway a 2nd time ti check the conditions
631+
gateway, err = assertGatewaySuccessful(t, "default", name)
632+
require.NoError(t, err, "failed waiting gateway to have conditions")
633+
require.Nil(t, condutils.FindStatusCondition(gateway.Status.Conditions, "DNSManaged"), "condition should not be present")
634+
require.Nil(t, condutils.FindStatusCondition(gateway.Status.Conditions, "DNSReady"), "condition should not be present")
635+
require.Nil(t, condutils.FindStatusCondition(gateway.Status.Conditions, "LoadBalancerManaged"), "condition should not be present")
636+
require.Nil(t, condutils.FindStatusCondition(gateway.Status.Conditions, "LoadBalancerReady"), "condition should not be present")
637+
})
638+
639+
t.Run("creating a new gateway with the wrong base domain should add openshift conditions reflecting the failure", func(t *testing.T) {
640+
rnd := rand.IntN(1000)
641+
name := fmt.Sprintf("gw-test-%d", rnd)
642+
testDomain := fmt.Sprintf("some-%d.not.something.managed.tld", rnd)
643+
gateway, err := createGateway(gatewayClass, name, operatorcontroller.DefaultOperandNamespace, testDomain)
644+
require.NoError(t, err, "failed to create gateway", "name", name)
645+
t.Cleanup(func() {
646+
require.NoError(t, client.IgnoreNotFound(kclient.Delete(context.TODO(), gateway)), "failed to clean test gateway", "name", name)
647+
})
648+
649+
gateway, err = assertGatewaySuccessful(t, operatorcontroller.DefaultOperandNamespace, name)
650+
require.NoError(t, err, "failed waiting gateway to be ready")
651+
652+
assert.Eventuallyf(t, func() bool {
653+
gw := &gatewayapiv1.Gateway{}
654+
nsName := types.NamespacedName{Namespace: operatorcontroller.DefaultOperandNamespace, Name: name}
655+
if err := kclient.Get(context.Background(), nsName, gw); err != nil {
656+
t.Logf("Failed to get gateway %v: %v; retrying...", nsName, err)
657+
return false
658+
}
659+
660+
if condutils.IsStatusConditionTrue(gw.Status.Conditions, "DNSManaged") &&
661+
condutils.IsStatusConditionPresentAndEqual(gw.Status.Conditions, "DNSReady", metav1.ConditionUnknown) &&
662+
condutils.IsStatusConditionTrue(gw.Status.Conditions, "LoadBalancerManaged") &&
663+
condutils.IsStatusConditionTrue(gw.Status.Conditions, "LoadBalancerReady") {
664+
665+
return true
666+
}
667+
t.Logf("conditions are not yet the expected: %v, retrying...", gw.Status.Conditions)
668+
return false
669+
}, 30*time.Second, 2*time.Second, "error waiting for openshift conditions to be present on Gateway")
670+
})
671+
672+
t.Run("creating a new gateway with the right base domain", func(t *testing.T) {
673+
rnd := rand.IntN(1000)
674+
name := fmt.Sprintf("gw-test-%d", rnd)
675+
testDomain := fmt.Sprintf("some-%d.%s", rnd, domain)
676+
gateway, err := createGateway(gatewayClass, name, operatorcontroller.DefaultOperandNamespace, testDomain)
677+
require.NoError(t, err, "failed to create gateway", "name", name)
678+
t.Cleanup(func() {
679+
require.NoError(t, client.IgnoreNotFound(kclient.Delete(context.TODO(), gateway)), "failed to clean test gateway", "name", name)
680+
})
681+
682+
gateway, err = assertGatewaySuccessful(t, operatorcontroller.DefaultOperandNamespace, name)
683+
require.NoError(t, err, "failed waiting gateway to be ready")
684+
685+
err = assertExpectedDNSRecords(t, map[expectedDnsRecord]bool{
686+
{dnsName: "*." + testDomain + ".", gatewayName: name}: true})
687+
688+
assert.NoError(t, err, "dnsrecord never got ready")
689+
690+
t.Run("should add openshift conditions", func(t *testing.T) {
691+
assert.Eventuallyf(t, func() bool {
692+
gw := &gatewayapiv1.Gateway{}
693+
nsName := types.NamespacedName{Namespace: operatorcontroller.DefaultOperandNamespace, Name: name}
694+
if err := kclient.Get(context.Background(), nsName, gw); err != nil {
695+
t.Logf("Failed to get gateway %v: %v; retrying...", nsName, err)
696+
return false
697+
}
698+
699+
if condutils.IsStatusConditionTrue(gw.Status.Conditions, "DNSManaged") &&
700+
condutils.IsStatusConditionTrue(gw.Status.Conditions, "DNSReady") &&
701+
condutils.IsStatusConditionTrue(gw.Status.Conditions, "LoadBalancerManaged") &&
702+
condutils.IsStatusConditionTrue(gw.Status.Conditions, "LoadBalancerReady") {
703+
704+
return true
705+
}
706+
t.Logf("conditions are not yet the expected: %v, retrying...", gw.Status.Conditions)
707+
return false
708+
}, 30*time.Second, 2*time.Second, "error waiting for openshift conditions to be present on Gateway")
709+
710+
// Check also for the existing event
711+
assert.Eventually(t, func() bool {
712+
events, err := getMatchingEventsFromGateway(t, kclient, gateway, "Normal", "AddedConditions")
713+
if err != nil {
714+
t.Logf("error fetching the events from namespace: %s; retrying...", err)
715+
return false
716+
}
717+
t.Logf("events found: (%d): %+v", len(events.Items), events.Items)
718+
return len(events.Items) > 0
719+
}, 30*time.Second, 2*time.Second, "error fetching matching resource to add conditions")
720+
})
721+
722+
t.Run("should bump openshift conditions when the gateway is changed", func(t *testing.T) {
723+
// Try to add a new infrastructure label, forcing the generation to bump
724+
originalGateway := &gatewayapiv1.Gateway{}
725+
assert.Eventually(t, func() bool {
726+
gw := &gatewayapiv1.Gateway{}
727+
nsName := types.NamespacedName{Namespace: operatorcontroller.DefaultOperandNamespace, Name: name}
728+
if err := kclient.Get(context.Background(), nsName, gw); err != nil {
729+
t.Logf("Failed to get gateway %v: %v; retrying...", nsName, err)
730+
return false
731+
}
732+
originalGateway = gw.DeepCopy()
733+
if gw.Spec.Infrastructure == nil {
734+
gw.Spec.Infrastructure = &gatewayapiv1.GatewayInfrastructure{}
735+
}
736+
if gw.Spec.Infrastructure.Labels == nil {
737+
gw.Spec.Infrastructure.Labels = make(map[gatewayapiv1.LabelKey]gatewayapiv1.LabelValue)
738+
}
739+
740+
gw.Spec.Infrastructure.Labels["something"] = "somelabel"
741+
742+
if err := kclient.Patch(context.Background(), gw, client.MergeFrom(originalGateway)); err != nil {
743+
t.Logf("failed to patch gateway %v: %v; retrying...", nsName, err)
744+
return false
745+
}
746+
return true
747+
}, 30*time.Second, 2*time.Second, "timeout waiting to patch the gateway")
748+
749+
gw := &gatewayapiv1.Gateway{}
750+
// Get the Gateway and check if conditions are there, and if their generation are different from the originalGateway value
751+
assert.Eventually(t, func() bool {
752+
nsName := types.NamespacedName{Namespace: operatorcontroller.DefaultOperandNamespace, Name: name}
753+
if err := kclient.Get(context.Background(), nsName, gw); err != nil {
754+
t.Logf("Failed to get gateway %v: %v; retrying...", nsName, err)
755+
return false
756+
}
757+
758+
dnsManaged := condutils.FindStatusCondition(gw.Status.Conditions, "DNSManaged")
759+
dnsReady := condutils.FindStatusCondition(gw.Status.Conditions, "DNSReady")
760+
loadBalancerManaged := condutils.FindStatusCondition(gw.Status.Conditions, "LoadBalancerManaged")
761+
loadBalancerReady := condutils.FindStatusCondition(gw.Status.Conditions, "LoadBalancerReady")
762+
763+
// Check if all conditions are not null and have a different generation from the original one
764+
// before adding the label
765+
if (dnsManaged != nil && dnsManaged.ObservedGeneration != originalGateway.Generation) &&
766+
(dnsReady != nil && dnsReady.ObservedGeneration != originalGateway.Generation) &&
767+
(loadBalancerManaged != nil && loadBalancerManaged.ObservedGeneration != originalGateway.Generation) &&
768+
(loadBalancerReady != nil && loadBalancerReady.ObservedGeneration != originalGateway.Generation) {
769+
return true
770+
}
771+
772+
t.Logf("conditions are not yet the expected: %v, retrying...", gw.Status.Conditions)
773+
return false
774+
}, 30*time.Second, 2*time.Second, "error waiting for openshift conditions to be present on Gateway")
775+
// We expect exactly 6 conditions. If we get more than it, Istio is adding
776+
// more conditions and we need to be aware that Gateway API status.conditons has a maxItems of 8
777+
assert.Len(t, gw.Status.Conditions, 6)
778+
// We expect an event to happen, so try to get this event to guarantee it was properly added
779+
assert.Eventually(t, func() bool {
780+
events, err := getMatchingEventsFromGateway(t, kclient, gw, "Normal", "AddedConditions")
781+
if err != nil {
782+
t.Logf("error fetching the events from namespace: %s; retrying...", err)
783+
return false
784+
}
785+
t.Logf("events found: (%d): %+v", len(events.Items), events.Items)
786+
return len(events.Items) > 0
787+
}, 30*time.Second, 5*time.Second, "error fetching matching resource to add conditions")
788+
})
789+
790+
// This test will delete the Gateway service. This should kick a new reconciliation
791+
// from Istio to recreate the services, and the condition "Programmed" should have
792+
// a different lastTransitionTime before the service being deleted.
793+
// But the condition "DNSManaged" and "LoadBalancerManaged"
794+
// should have the original timestamp, meaning they weren't changed
795+
t.Run("should not replace openshift conditions when Istio reconciles the gateway", func(t *testing.T) {
796+
originalGateway := &gatewayapiv1.Gateway{}
797+
nsName := types.NamespacedName{Namespace: operatorcontroller.DefaultOperandNamespace, Name: name}
798+
require.Eventually(t, func() bool {
799+
if err := kclient.Get(context.Background(), nsName, originalGateway); err != nil {
800+
t.Logf("Failed to get gateway %v: %v; retrying...", nsName, err)
801+
return false
802+
}
803+
return true
804+
}, 30*time.Second, 2*time.Second)
805+
806+
// These lastTransitionTime should not change
807+
// Also do a sanity check that they are true / ready
808+
originalDNSManagedCondition := condutils.FindStatusCondition(originalGateway.Status.Conditions, "DNSManaged")
809+
require.NotNil(t, originalDNSManagedCondition)
810+
require.Equal(t, metav1.ConditionTrue, originalDNSManagedCondition.Status)
811+
originalLoadBalancerManagedCondition := condutils.FindStatusCondition(originalGateway.Status.Conditions, "LoadBalancerManaged")
812+
require.NotNil(t, originalLoadBalancerManagedCondition)
813+
require.Equal(t, metav1.ConditionTrue, originalLoadBalancerManagedCondition.Status)
814+
815+
// These lastTransitionTime should change once the service is deleted and reprovisioned
816+
originalLoadBalancerReadyCondition := condutils.FindStatusCondition(originalGateway.Status.Conditions, "LoadBalancerReady")
817+
require.NotNil(t, originalLoadBalancerReadyCondition)
818+
require.Equal(t, metav1.ConditionTrue, originalLoadBalancerReadyCondition.Status)
819+
originalProgrammedCondition := condutils.FindStatusCondition(originalGateway.Status.Conditions, "Programmed")
820+
require.NotNil(t, originalProgrammedCondition)
821+
require.Equal(t, metav1.ConditionTrue, originalProgrammedCondition.Status)
822+
823+
t.Run("deleting a service managed by Istio", func(t *testing.T) {
824+
ctx := context.Background()
825+
svcList := &corev1.ServiceList{}
826+
assert.Eventually(t, func() bool {
827+
if err := kclient.List(ctx, svcList,
828+
client.InNamespace(operatorcontroller.DefaultOperandNamespace),
829+
client.MatchingLabels{operatorcontroller.GatewayNameLabelKey: originalGateway.GetName()},
830+
); err != nil {
831+
t.Logf("Failed to list services attached to Gateway %s; retrying...: %s", originalGateway.GetName(), err)
832+
return false
833+
}
834+
return true
835+
}, 30*time.Second, 2*time.Second)
836+
837+
require.Len(t, svcList.Items, 1)
838+
svc := svcList.Items[0]
839+
840+
// Delete the service
841+
assert.Eventually(t, func() bool {
842+
if err := kclient.Delete(ctx, &svc); client.IgnoreNotFound(err) != nil {
843+
t.Logf("Failed to delete service %s attached to Gateway %s; retrying...: %s", svc.GetName(), originalGateway.GetName(), err)
844+
return false
845+
}
846+
return true
847+
}, 30*time.Second, time.Second)
848+
})
849+
850+
currentGateway := &gatewayapiv1.Gateway{}
851+
var currentDNSManagedCondition, currentLoadBalancerManagedCondition *metav1.Condition
852+
t.Run("lastTransitionTime should change for some conditions and not for others", func(t *testing.T) {
853+
assert.Eventually(t, func() bool {
854+
855+
nsName := types.NamespacedName{Namespace: operatorcontroller.DefaultOperandNamespace, Name: name}
856+
857+
if err := kclient.Get(context.Background(), nsName, currentGateway); err != nil {
858+
t.Logf("Failed to get current gateway %v: %v; retrying...", nsName, err)
859+
return false
860+
}
861+
862+
currentDNSManagedCondition = condutils.FindStatusCondition(currentGateway.Status.Conditions, "DNSManaged")
863+
currentLoadBalancerManagedCondition = condutils.FindStatusCondition(currentGateway.Status.Conditions, "LoadBalancerManaged")
864+
currentLoadBalancerReadyCondition := condutils.FindStatusCondition(currentGateway.Status.Conditions, "LoadBalancerReady")
865+
currentProgrammedCondition := condutils.FindStatusCondition(currentGateway.Status.Conditions, "Programmed")
866+
867+
// Expect conditions to be ready
868+
if (currentDNSManagedCondition == nil || currentDNSManagedCondition.Status != metav1.ConditionTrue) ||
869+
(currentLoadBalancerManagedCondition == nil || currentLoadBalancerManagedCondition.Status != metav1.ConditionTrue) ||
870+
(currentLoadBalancerReadyCondition == nil || currentLoadBalancerReadyCondition.Status != metav1.ConditionTrue) ||
871+
(currentProgrammedCondition == nil || currentProgrammedCondition.Status != metav1.ConditionTrue) {
872+
873+
t.Logf("conditions on gateway %s are not ready yet: %+v", currentGateway.GetName(), currentGateway.Status.Conditions)
874+
return false
875+
}
876+
877+
// Expect LoadBalancerReady and Programmed condition to have a new transition time
878+
if !currentLoadBalancerReadyCondition.LastTransitionTime.After(originalLoadBalancerReadyCondition.LastTransitionTime.Time) ||
879+
!currentProgrammedCondition.LastTransitionTime.After(originalProgrammedCondition.LastTransitionTime.Time) {
880+
t.Logf("conditions on gateway %s didn't changed yet: %+v", currentGateway.GetName(), currentGateway.Status.Conditions)
881+
return false
882+
}
883+
884+
return true
885+
}, 3*time.Minute, 3*time.Second)
886+
})
887+
// After conditions are bumped, the original ones should not change
888+
assert.Equal(t, originalDNSManagedCondition.LastTransitionTime, currentDNSManagedCondition.LastTransitionTime, "the DNSManaged condition LastTransitionTime should not change")
889+
assert.Equal(t, originalLoadBalancerManagedCondition.LastTransitionTime, currentLoadBalancerManagedCondition.LastTransitionTime, "the LoadBalancerManaged condition LastTransitionTime should not change")
890+
891+
})
892+
893+
})
894+
}
895+
896+
func getMatchingEventsFromGateway(t *testing.T, kclient client.Reader, gw *gatewayapiv1.Gateway, eventType, reason string) (*corev1.EventList, error) {
897+
t.Helper()
898+
operandEvents := &corev1.EventList{}
899+
fieldSelector := fields.Set{
900+
"involvedObject.kind": "Gateway",
901+
"involvedObject.namespace": gw.Namespace,
902+
"involvedObject.name": gw.Name,
903+
"type": eventType,
904+
"reason": reason,
905+
}
906+
907+
t.Logf("using field selector: %+v", fieldSelector)
590908

909+
err := kclient.List(context.Background(), operandEvents,
910+
client.InNamespace(operatorcontroller.DefaultOperandNamespace),
911+
client.MatchingFieldsSelector{Selector: fields.SelectorFromSet(fieldSelector)})
912+
return operandEvents, err
591913
}
592914

593915
func testGatewayAPIDNSListenerUpdate(t *testing.T) {

0 commit comments

Comments
 (0)