blob: e7ab91b11221b0a7169e84eb59f430f1fcb84e30 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package conformance
import (
"context"
"fmt"
"os"
"testing"
"time"
"github.com/gruntwork-io/terratest/modules/k8s"
"github.com/gruntwork-io/terratest/modules/retry"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"sigs.k8s.io/controller-runtime/pkg/client"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
"github.com/apache/apisix-ingress-controller/internal/types"
"github.com/apache/apisix-ingress-controller/test/e2e/framework"
"github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
)
var gatewayClassName = "apisix"
var controllerName = "apisix.apache.org/apisix-ingress-controller"
var gatewayClass = fmt.Sprintf(`
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: %s
spec:
controllerName: %s
`, gatewayClassName, controllerName)
var gatewayProxyYaml = `
apiVersion: apisix.apache.org/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",
"apisix-conformance-test",
}
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()
// init newDeployer function
scaffold.NewDeployer = scaffold.NewAPISIXDeployer
// Check and delete specific namespaces if they exist
kubectl := k8s.NewKubectlOptions("", "", "default")
deleteNamespace(kubectl)
namespace := "apisix-conformance-test"
k8s.KubectlApplyFromString(GinkgoT(), kubectl, gatewayClass)
defer k8s.KubectlDeleteFromString(GinkgoT(), kubectl, gatewayClass)
k8s.CreateNamespace(GinkgoT(), kubectl, namespace)
defer k8s.DeleteNamespace(GinkgoT(), kubectl, namespace)
adminkey := getEnvOrDefault("APISIX_ADMIN_KEY", "edd1c9f034335f136f87ad84b625c8f1")
controllerName := "apisix.apache.org/apisix-ingress-controller"
s := scaffold.NewScaffold(scaffold.Options{
ControllerName: controllerName,
SkipHooks: true,
APISIXAdminAPIKey: adminkey,
})
s.Deployer.DeployDataplane(scaffold.DeployDataplaneOptions{
AdminKey: adminkey,
Namespace: namespace,
SkipCreateTunnels: true,
ServiceType: "LoadBalancer",
ServiceHTTPPort: 80,
ServiceHTTPSPort: 443,
})
svc := s.GetDataplaneService()
if len(svc.Status.LoadBalancer.Ingress) == 0 {
Fail("No LoadBalancer found for the service")
}
address := svc.Status.LoadBalancer.Ingress[0].IP
f.DeployIngress(framework.IngressDeployOpts{
ControllerName: controllerName,
Namespace: namespace,
StatusAddress: address,
InitSyncDelay: 20 * time.Minute,
ProviderType: framework.ProviderType,
ProviderSyncPeriod: 1 * time.Hour,
})
adminEndpoint := fmt.Sprintf("http://%s.%s:9180", svc.Name, namespace)
defaultGatewayProxyOpts = GatewayProxyOpts{
StatusAddress: address,
AdminKey: adminkey,
AdminEndpoint: adminEndpoint,
}
patchGatewaysForConformanceTest(context.Background(), f.K8sClient)
code := m.Run()
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: "apisix.apache.org",
Kind: types.KindGatewayProxy,
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)
}
}
}()
}
// getEnvOrDefault returns environment variable value or default
func getEnvOrDefault(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}