diff --git a/changelog/v1.18.0-rc2/issue_10397.yaml b/changelog/v1.18.0-rc2/issue_10397.yaml new file mode 100644 index 00000000000..874b65c5577 --- /dev/null +++ b/changelog/v1.18.0-rc2/issue_10397.yaml @@ -0,0 +1,6 @@ +changelog: + - type: NON_USER_FACING + issueLink: https://github.com/solo-io/gloo/issues/10397 + resolvesIssue: false + description: >- + Adds ginkgo assertions for resource status of TCPRoute e2e tests. diff --git a/test/kubernetes/e2e/features/services/tcproute/suite.go b/test/kubernetes/e2e/features/services/tcproute/suite.go index 4906a1249ab..3e6c402264d 100644 --- a/test/kubernetes/e2e/features/services/tcproute/suite.go +++ b/test/kubernetes/e2e/features/services/tcproute/suite.go @@ -4,6 +4,8 @@ import ( "context" "github.com/stretchr/testify/suite" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "sigs.k8s.io/gateway-api/apis/v1" "github.com/solo-io/gloo/pkg/utils/kubeutils" "github.com/solo-io/gloo/pkg/utils/requestutils/curl" @@ -42,14 +44,19 @@ func (s *testingSuite) TestConfigureTCPRouteBackingDestinationsWithSingleService err := s.testInstallation.Actions.Kubectl().ApplyFile(s.ctx, singleListenerGatewayAndClientManifest) s.Assert().NoError(err, "can apply gateway and client manifest") + s.testInstallation.Assertions.EventuallyGatewayCondition(s.ctx, "tcp-gateway", "default", v1.GatewayConditionAccepted, metav1.ConditionTrue, timeout) err = s.testInstallation.Actions.Kubectl().ApplyFile(s.ctx, multiBackendServiceManifest) s.Assert().NoError(err, "can apply backend service manifest") + s.testInstallation.Assertions.EventuallyObjectsExist(s.ctx, proxyService, proxyDeployment) err = s.testInstallation.Actions.Kubectl().ApplyFile(s.ctx, singleTcpRouteManifest) s.Assert().NoError(err, "can apply tcproute manifest") + s.testInstallation.Assertions.EventuallyTCPRouteCondition(s.ctx, "tcp-app-1", "default", v1.RouteConditionAccepted, metav1.ConditionTrue, timeout) + s.testInstallation.Assertions.EventuallyTCPRouteCondition(s.ctx, "tcp-app-1", "default", v1.RouteConditionResolvedRefs, metav1.ConditionTrue, timeout) + s.testInstallation.Assertions.EventuallyGatewayCondition(s.ctx, "tcp-gateway", "default", v1.GatewayConditionProgrammed, metav1.ConditionTrue, timeout) + s.testInstallation.Assertions.EventuallyGatewayListenerAttachedRoutes(s.ctx, "tcp-gateway", "default", v1.SectionName("foo"), int32(1), timeout) - s.testInstallation.Assertions.EventuallyObjectsExist(s.ctx, proxyService, proxyDeployment) s.testInstallation.Assertions.AssertEventualCurlResponse( s.ctx, defaults.CurlPodExecOpt, @@ -81,14 +88,22 @@ func (s *testingSuite) TestConfigureTCPRouteBackingDestinationsWithMultiServices err := s.testInstallation.Actions.Kubectl().ApplyFile(s.ctx, multiListenerGatewayAndClientManifest) s.Assert().NoError(err, "can apply gateway and client manifest") + s.testInstallation.Assertions.EventuallyGatewayCondition(s.ctx, "tcp-gateway", "default", v1.GatewayConditionAccepted, metav1.ConditionTrue, timeout) err = s.testInstallation.Actions.Kubectl().ApplyFile(s.ctx, multiBackendServiceManifest) s.Assert().NoError(err, "can apply backend service manifest") + s.testInstallation.Assertions.EventuallyObjectsExist(s.ctx, proxyService, proxyDeployment) err = s.testInstallation.Actions.Kubectl().ApplyFile(s.ctx, multiTcpRouteManifest) s.Assert().NoError(err, "can apply tcproute manifest") + s.testInstallation.Assertions.EventuallyTCPRouteCondition(s.ctx, "tcp-app-1", "default", v1.RouteConditionAccepted, metav1.ConditionTrue, timeout) + s.testInstallation.Assertions.EventuallyTCPRouteCondition(s.ctx, "tcp-app-1", "default", v1.RouteConditionResolvedRefs, metav1.ConditionTrue, timeout) + s.testInstallation.Assertions.EventuallyTCPRouteCondition(s.ctx, "tcp-app-2", "default", v1.RouteConditionAccepted, metav1.ConditionTrue, timeout) + s.testInstallation.Assertions.EventuallyTCPRouteCondition(s.ctx, "tcp-app-2", "default", v1.RouteConditionResolvedRefs, metav1.ConditionTrue, timeout) + s.testInstallation.Assertions.EventuallyGatewayCondition(s.ctx, "tcp-gateway", "default", v1.GatewayConditionProgrammed, metav1.ConditionTrue, timeout) + s.testInstallation.Assertions.EventuallyGatewayListenerAttachedRoutes(s.ctx, "tcp-gateway", "default", v1.SectionName("foo"), int32(1), timeout) + s.testInstallation.Assertions.EventuallyGatewayListenerAttachedRoutes(s.ctx, "tcp-gateway", "default", v1.SectionName("bar"), int32(1), timeout) - s.testInstallation.Assertions.EventuallyObjectsExist(s.ctx, proxyService, proxyDeployment) s.testInstallation.Assertions.AssertEventualCurlResponse( s.ctx, defaults.CurlPodExecOpt, diff --git a/test/kubernetes/e2e/features/services/tcproute/types.go b/test/kubernetes/e2e/features/services/tcproute/types.go index da83798ce11..def3b71ce75 100644 --- a/test/kubernetes/e2e/features/services/tcproute/types.go +++ b/test/kubernetes/e2e/features/services/tcproute/types.go @@ -3,6 +3,7 @@ package tcproute import ( "net/http" "path/filepath" + "time" testmatchers "github.com/solo-io/gloo/test/gomega/matchers" @@ -20,6 +21,8 @@ var ( multiTcpRouteManifest = filepath.Join(util.MustGetThisDir(), "testdata", "multi-tcproute.yaml") singleListenerGatewayAndClientManifest = filepath.Join(util.MustGetThisDir(), "testdata", "single-listener-gateway-and-client.yaml") singleTcpRouteManifest = filepath.Join(util.MustGetThisDir(), "testdata", "single-tcproute.yaml") + // Test assertion timeout + timeout = 30 * time.Second // Proxy resource to be translated glooProxyObjectMeta = metav1.ObjectMeta{ diff --git a/test/kubernetes/testutils/assertions/status.go b/test/kubernetes/testutils/assertions/status.go index 1da989add0c..de41c8fa343 100644 --- a/test/kubernetes/testutils/assertions/status.go +++ b/test/kubernetes/testutils/assertions/status.go @@ -2,6 +2,7 @@ package assertions import ( "context" + "fmt" "time" "github.com/onsi/ginkgo/v2" @@ -15,6 +16,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" gwv1 "sigs.k8s.io/gateway-api/apis/v1" + gwv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" ) // Checks GetNamespacedStatuses status for gloo installation namespace @@ -162,3 +164,90 @@ func (p *Provider) EventuallyHTTPRouteStatusContainsReason( g.Expect(route.Status.RouteStatus).To(gomega.HaveValue(matcher)) }, currentTimeout, pollingInterval).Should(gomega.Succeed()) } + +// EventuallyGatewayCondition checks the provided Gateway condition is set to expect. +func (p *Provider) EventuallyGatewayCondition( + ctx context.Context, + gatewayName string, + gatewayNamespace string, + cond gwv1.GatewayConditionType, + expect metav1.ConditionStatus, + timeout ...time.Duration, +) { + ginkgo.GinkgoHelper() + currentTimeout, pollingInterval := helper.GetTimeouts(timeout...) + p.Gomega.Eventually(func(g gomega.Gomega) { + gateway := &gwv1.Gateway{} + err := p.clusterContext.Client.Get(ctx, types.NamespacedName{Name: gatewayName, Namespace: gatewayNamespace}, gateway) + g.Expect(err).NotTo(gomega.HaveOccurred(), "failed to get Gateway") + + condition := getConditionByType(gateway.Status.Conditions, string(cond)) + g.Expect(condition).NotTo(gomega.BeNil(), fmt.Sprintf("%v condition not found", cond)) + g.Expect(condition.Status).To(gomega.Equal(expect), fmt.Sprintf("%v condition is not %v", cond, expect)) + }, currentTimeout, pollingInterval).Should(gomega.Succeed()) +} + +// EventuallyGatewayListenerAttachedRoutes checks the provided Gateway contains the expected attached routes for the listener. +func (p *Provider) EventuallyGatewayListenerAttachedRoutes( + ctx context.Context, + gatewayName string, + gatewayNamespace string, + listener gwv1.SectionName, + routes int32, + timeout ...time.Duration, +) { + ginkgo.GinkgoHelper() + currentTimeout, pollingInterval := helper.GetTimeouts(timeout...) + p.Gomega.Eventually(func(g gomega.Gomega) { + gateway := &gwv1.Gateway{} + err := p.clusterContext.Client.Get(ctx, types.NamespacedName{Name: gatewayName, Namespace: gatewayNamespace}, gateway) + g.Expect(err).NotTo(gomega.HaveOccurred(), "failed to get Gateway") + + found := false + for _, l := range gateway.Status.Listeners { + if l.Name == listener { + found = true + g.Expect(l.AttachedRoutes).To(gomega.Equal(routes), fmt.Sprintf("%v listener does not contain %d attached routes", l, routes)) + } + } + g.Expect(found).To(gomega.BeTrue(), fmt.Sprintf("%v listener not found", listener)) + }, currentTimeout, pollingInterval).Should(gomega.Succeed()) +} + +// EventuallyTCPRouteCondition checks that provided TCPRoute condition is set to expect. +func (p *Provider) EventuallyTCPRouteCondition( + ctx context.Context, + routeName string, + routeNamespace string, + cond gwv1.RouteConditionType, + expect metav1.ConditionStatus, + timeout ...time.Duration, +) { + ginkgo.GinkgoHelper() + currentTimeout, pollingInterval := helper.GetTimeouts(timeout...) + p.Gomega.Eventually(func(g gomega.Gomega) { + route := &gwv1a2.TCPRoute{} + err := p.clusterContext.Client.Get(ctx, types.NamespacedName{Name: routeName, Namespace: routeNamespace}, route) + g.Expect(err).NotTo(gomega.HaveOccurred(), "failed to get TCPRoute") + + var conditionFound bool + for _, parentStatus := range route.Status.Parents { + condition := getConditionByType(parentStatus.Conditions, string(cond)) + if condition != nil && condition.Status == expect { + conditionFound = true + break + } + } + g.Expect(conditionFound).To(gomega.BeTrue(), fmt.Sprintf("%v condition is not %v for any parent", cond, expect)) + }, currentTimeout, pollingInterval).Should(gomega.Succeed()) +} + +// Helper function to retrieve a condition by type from a list of conditions. +func getConditionByType(conditions []metav1.Condition, conditionType string) *metav1.Condition { + for _, condition := range conditions { + if condition.Type == conditionType { + return &condition + } + } + return nil +} diff --git a/test/kubernetes/testutils/cluster/kind.go b/test/kubernetes/testutils/cluster/kind.go index 1f0b2c2e61b..c6b0cea50a7 100644 --- a/test/kubernetes/testutils/cluster/kind.go +++ b/test/kubernetes/testutils/cluster/kind.go @@ -19,7 +19,7 @@ import ( // MustKindContext returns the Context for a KinD cluster with the given name func MustKindContext(clusterName string) *Context { - return MustKindContextWithScheme(clusterName, schemes.DefaultScheme()) + return MustKindContextWithScheme(clusterName, schemes.TestingScheme()) } // MustKindContextWithScheme returns the Context for a KinD cluster with the given name and scheme