| // 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 |
| } |