Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CI: Adds Ginkgo Resource Condition Assertions #10398

Merged
merged 4 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions changelog/v1.18.0-rc2/issue_10397.yaml
Original file line number Diff line number Diff line change
@@ -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.
19 changes: 17 additions & 2 deletions test/kubernetes/e2e/features/services/tcproute/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Do you think it would be valuable to assert on a list of conditions?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was considering ^ but I decided to start simple, mainly b/c we need to check the condition status for the different types, e.g. Accecpted=True while Conflicted=False.

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,
Expand Down
3 changes: 3 additions & 0 deletions test/kubernetes/e2e/features/services/tcproute/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tcproute
import (
"net/http"
"path/filepath"
"time"

testmatchers "github.com/solo-io/gloo/test/gomega/matchers"

Expand All @@ -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{
Expand Down
89 changes: 89 additions & 0 deletions test/kubernetes/testutils/assertions/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package assertions

import (
"context"
"fmt"
"time"

"github.com/onsi/ginkgo/v2"
Expand All @@ -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
Expand Down Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion test/kubernetes/testutils/cluster/kind.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏

}

// MustKindContextWithScheme returns the Context for a KinD cluster with the given name and scheme
Expand Down
Loading