Skip to content
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ api7-ingress-controller
api7-ingress-controller-conformance-report.yaml

*.mdx
.cursor/

168 changes: 166 additions & 2 deletions test/conformance/suite_test.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package conformance

import (
"context"
"fmt"
"os"
"testing"
"time"

"github.com/api7/api7-ingress-controller/test/e2e/framework"
"github.com/gruntwork-io/terratest/modules/k8s"
"github.com/gruntwork-io/terratest/modules/retry"
"sigs.k8s.io/controller-runtime/pkg/client"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
)

var gatewayClassName = "api7"
Expand All @@ -24,15 +29,89 @@ spec:
controllerName: %s
`, gatewayClassName, controllerName)

var gatewayProxyYaml = `
apiVersion: gateway.apisix.io/v1alpha1
kind: GatewayProxy
metadata:
name: conformance-gateway-proxy
namespace: %s
spec:
statusAddress:
- %s
provider:
type: ControlPlane
controlPlane:
endpoints:
- %s
auth:
type: AdminKey
adminKey:
value: %s
`

type GatewayProxyOpts struct {
StatusAddress string
AdminKey string
AdminEndpoint string
}

var defaultGatewayProxyOpts GatewayProxyOpts

func deleteNamespace(kubectl *k8s.KubectlOptions) {
// gateway api conformance test namespaces
namespacesToDelete := []string{
"gateway-conformance-infra",
"gateway-conformance-web-backend",
"gateway-conformance-app-backend",
}

for _, ns := range namespacesToDelete {
_, err := k8s.GetNamespaceE(GinkgoT(), kubectl, ns)
if err == nil {
// Namespace exists, delete it
GinkgoT().Logf("Deleting existing namespace: %s", ns)
err := k8s.DeleteNamespaceE(GinkgoT(), kubectl, ns)
if err != nil {
GinkgoT().Logf("Error deleting namespace %s: %v", ns, err)
continue
}

// Wait for deletion to complete by checking until GetNamespaceE returns an error
_, err = retry.DoWithRetryE(
GinkgoT(),
fmt.Sprintf("Waiting for namespace %s to be deleted", ns),
30,
5*time.Second,
func() (string, error) {
_, err := k8s.GetNamespaceE(GinkgoT(), kubectl, ns)
if err != nil {
// Namespace is gone, which is what we want
return "Namespace deleted", nil
}
return "", fmt.Errorf("namespace %s still exists", ns)
},
)

if err != nil {
GinkgoT().Logf("Error waiting for namespace %s to be deleted: %v", ns, err)
}
} else {
GinkgoT().Logf("Namespace %s does not exist or cannot be accessed", ns)
}
}
}

func TestMain(m *testing.M) {
RegisterFailHandler(Fail)
f := framework.NewFramework()

f.BeforeSuite()

namespace := "api7ee-conformance-test"

// Check and delete specific namespaces if they exist
kubectl := k8s.NewKubectlOptions("", "", "default")
deleteNamespace(kubectl)

namespace := "api7ee-conformance-test"

k8s.KubectlApplyFromString(GinkgoT(), kubectl, gatewayClass)
defer k8s.KubectlDeleteFromString(GinkgoT(), kubectl, gatewayClass)
Expand Down Expand Up @@ -71,9 +150,94 @@ func TestMain(m *testing.M) {
StatusAddress: address,
})

defaultGatewayProxyOpts = GatewayProxyOpts{
StatusAddress: address,
AdminKey: adminKey,
AdminEndpoint: framework.DashboardTLSEndpoint,
}

patchGatewaysForConformanceTest(context.Background(), f.K8sClient)
Copy link

Copilot AI Apr 22, 2025

Choose a reason for hiding this comment

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

Consider using a cancellable context instead of context.Background() when starting the patching goroutine to allow for proper termination and resource cleanup after tests.

Copilot uses AI. Check for mistakes.

code := m.Run()

f.AfterSuite()

os.Exit(code)
}

func patchGatewaysForConformanceTest(ctx context.Context, k8sClient client.Client) {
var gatewayProxyMap = make(map[string]bool)

// list all gateways and patch them
patchGateway := func(ctx context.Context, k8sClient client.Client) bool {
gatewayList := &gatewayv1.GatewayList{}
if err := k8sClient.List(ctx, gatewayList); err != nil {
return false
}

patched := false
for i := range gatewayList.Items {
gateway := &gatewayList.Items[i]

// check if the gateway already has infrastructure.parametersRef
if gateway.Spec.Infrastructure != nil &&
gateway.Spec.Infrastructure.ParametersRef != nil {
continue
}

GinkgoT().Logf("Patching Gateway %s", gateway.Name)
// check if the gateway proxy has been created, if not, create it
if !gatewayProxyMap[gateway.Namespace] {
gatewayProxy := fmt.Sprintf(gatewayProxyYaml,
gateway.Namespace,
defaultGatewayProxyOpts.StatusAddress,
defaultGatewayProxyOpts.AdminEndpoint,
defaultGatewayProxyOpts.AdminKey)
kubectl := k8s.NewKubectlOptions("", "", gateway.Namespace)
k8s.KubectlApplyFromString(GinkgoT(), kubectl, gatewayProxy)

// Mark this namespace as having a GatewayProxy
gatewayProxyMap[gateway.Namespace] = true
}

// add infrastructure.parametersRef
gateway.Spec.Infrastructure = &gatewayv1.GatewayInfrastructure{
ParametersRef: &gatewayv1.LocalParametersReference{
Group: "gateway.apisix.io",
Kind: "GatewayProxy",
Name: "conformance-gateway-proxy",
},
}

if err := k8sClient.Update(ctx, gateway); err != nil {
GinkgoT().Logf("Failed to patch Gateway %s: %v", gateway.Name, err)
continue
}

patched = true
GinkgoT().Logf("Successfully patched Gateway %s with GatewayProxy reference", gateway.Name)
}

return patched
}

// continuously monitor and patch gateway resources
go func() {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()

for {
select {
case <-ctx.Done():
// clean up the gateway proxy
for namespace := range gatewayProxyMap {
kubectl := k8s.NewKubectlOptions("", "", namespace)
_ = k8s.RunKubectlE(GinkgoT(), kubectl, "delete", "gatewayproxy", "conformance-gateway-proxy")
}
return
case <-ticker.C:
patchGateway(ctx, k8sClient)
}
}
}()
}
6 changes: 6 additions & 0 deletions test/e2e/framework/framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var (
Expand Down Expand Up @@ -44,6 +45,7 @@ type Framework struct {
kubectlOpts *k8s.KubectlOptions
clientset *kubernetes.Clientset
restConfig *rest.Config
K8sClient client.Client

DB *gorm.DB
RawETCD *clientv3.Client
Expand Down Expand Up @@ -83,6 +85,10 @@ func NewFramework() *Framework {
f.GomegaT.Expect(err).ShouldNot(HaveOccurred(), "creating Kubernetes clientset")
f.clientset = clientset

k8sClient, err := client.New(restCfg, client.Options{})
f.GomegaT.Expect(err).ShouldNot(HaveOccurred(), "creating controller-runtime client")
f.K8sClient = k8sClient

_framework = f

// BeforeSuite(f.BeforeSuite)
Expand Down
Loading