This is an automated email from the ASF dual-hosted git repository.

alinsran pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git


The following commit(s) were added to refs/heads/master by this push:
     new 0c0c5f95 feat: support benchmark test (#2663)
0c0c5f95 is described below

commit 0c0c5f95f0274c673afe3cf4212253d760c59b15
Author: AlinsRan <[email protected]>
AuthorDate: Tue Dec 9 00:29:00 2025 +0800

    feat: support benchmark test (#2663)
---
 .github/workflows/benchmark-test.yml               | 113 ++++++
 Makefile                                           |  11 +-
 go.mod                                             |  11 +
 go.sum                                             |  22 ++
 internal/adc/cache/store.go                        |   2 +-
 internal/adc/client/client.go                      |   1 +
 internal/controller/status/updater.go              |   7 +-
 internal/controller/utils.go                       |   2 +-
 internal/manager/readiness/manager.go              |  11 +-
 internal/provider/apisix/provider.go               |   9 +-
 test/benchmark/benchmark_test.go                   | 430 +++++++++++++++++++++
 test/benchmark/suite_test.go                       |  41 ++
 test/benchmark/utis.go                             |  76 ++++
 .../e2e/framework/manifests/apisix-standalone.yaml | 153 --------
 test/e2e/framework/manifests/apisix.yaml           |  20 +
 test/e2e/scaffold/scaffold.go                      | 106 +++++
 16 files changed, 848 insertions(+), 167 deletions(-)

diff --git a/.github/workflows/benchmark-test.yml 
b/.github/workflows/benchmark-test.yml
new file mode 100644
index 00000000..565fb1d0
--- /dev/null
+++ b/.github/workflows/benchmark-test.yml
@@ -0,0 +1,113 @@
+# 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.
+
+name: Benchmark Test
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+    branches:
+      - master
+  workflow_dispatch:
+    inputs:
+      routes:
+        description: "Number of routes for benchmark test"
+        required: false
+        default: "2000"
+      consumers:
+        description: "Number of consumers for benchmark test"
+        required: false
+        default: "2000"
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || 
github.ref }}
+  cancel-in-progress: true
+
+env:
+  ADC_VERSION: dev
+  BENCHMARK_ROUTES: ${{ github.event.inputs.routes }}
+  BENCHMARK_CONSUMERS: ${{ github.event.inputs.consumers }}
+
+jobs:
+  e2e-test:
+    strategy:
+      matrix:
+        provider_type:
+        - apisix-standalone
+        - apisix
+      fail-fast: false
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+        with:
+          submodules: recursive
+
+      - name: Setup Go Env
+        uses: actions/setup-go@v4
+        with:
+          go-version: "1.24"
+
+      - name: Install kind
+        run: |
+          go install sigs.k8s.io/[email protected]
+
+      - name: Install ginkgo
+        run: |
+            make install-ginkgo
+
+      - name: Build images
+        env:
+          TAG: dev
+          ARCH: amd64
+          ENABLE_PROXY: "false"
+          BASE_IMAGE_TAG: "debug"
+        run: |
+          echo "building images..."
+          make build-image
+
+      - name: Launch Kind Cluster
+        run: |
+          make kind-up
+
+      - name: Loading Docker Image to Kind Cluster
+        run: |
+          make kind-load-images
+
+      - name: Install Gateway API And CRDs
+        run: |
+          make install
+
+      - name: Extract adc binary
+        if: ${{ env.ADC_VERSION == 'dev' }}
+        run: |
+          docker create --name adc-temp ghcr.io/api7/adc:dev
+          docker cp adc-temp:main.js adc.js
+          docker rm adc-temp
+          node $(pwd)/adc.js -v
+          echo "ADC_BIN=node $(pwd)/adc.js" >> $GITHUB_ENV
+
+      - name: Run Benchmark Test
+        shell: bash
+        env:
+          PROVIDER_TYPE: ${{ matrix.provider_type }}
+          TEST_LABEL: ${{ matrix.cases_subset }}
+          TEST_ENV: CI
+        run: |
+          make benchmark-test
diff --git a/Makefile b/Makefile
index 69b94c71..037621e5 100644
--- a/Makefile
+++ b/Makefile
@@ -27,7 +27,7 @@ IMG ?= apache/apisix-ingress-controller:$(IMAGE_TAG)
 ENVTEST_K8S_VERSION = 1.30.0
 KIND_NAME ?= apisix-ingress-cluster
 
-ADC_VERSION ?= 0.21.2
+ADC_VERSION ?= 0.23.1
 
 DIR := $(shell pwd)
 
@@ -57,6 +57,9 @@ CONFORMANCE_TEST_REPORT_OUTPUT ?= 
$(DIR)/apisix-ingress-controller-conformance-r
 ## 
https://github.com/kubernetes-sigs/gateway-api/blob/v1.3.0/conformance/utils/suite/profiles.go
 CONFORMANCE_PROFILES ?= GATEWAY-HTTP,GATEWAY-GRPC,GATEWAY-TLS
 
+TEST_EXCLUDES ?= /e2e /conformance /benchmark
+TEST_PACKAGES = $(shell go list ./... $(foreach p,$(TEST_EXCLUDES),| grep -v 
$(p)))
+
 # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is 
set)
 ifeq (,$(shell go env GOBIN))
 GOBIN=$(shell go env GOPATH)/bin
@@ -128,7 +131,7 @@ vet: ## Run go vet against code.
 
 .PHONY: test
 test: manifests generate fmt vet envtest ## Run tests.
-       KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) 
--bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e | grep 
-v /conformance) -coverprofile cover.out
+       KUBEBUILDER_ASSETS="$$( $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir 
$(LOCALBIN) -p path )" go test $(TEST_PACKAGES) -coverprofile cover.out
 
 .PHONY: kind-e2e-test
 kind-e2e-test: kind-up build-image kind-load-images e2e-test
@@ -153,6 +156,10 @@ conformance-test:
                --conformance-profiles=$(CONFORMANCE_PROFILES) \
                --report-output=$(CONFORMANCE_TEST_REPORT_OUTPUT)
 
+.PHONY: benchmark-test
+benchmark-test:
+       go test -v ./test/benchmark -test.timeout=$(TEST_TIMEOUT) -v -ginkgo.v
+
 .PHONY: lint
 lint: sort-import golangci-lint ## Run golangci-lint linter
        $(GOLANGCI_LINT) run
diff --git a/go.mod b/go.mod
index 60da4110..757f8e28 100644
--- a/go.mod
+++ b/go.mod
@@ -18,8 +18,10 @@ require (
        github.com/hashicorp/go-memdb v1.3.4
        github.com/imdario/mergo v0.3.16
        github.com/incubator4/go-resty-expr v0.1.1
+       github.com/olekukonko/tablewriter v1.1.1
        github.com/onsi/ginkgo/v2 v2.22.0
        github.com/onsi/gomega v1.36.1
+       github.com/panjf2000/ants/v2 v2.11.3
        github.com/pkg/errors v0.9.1
        github.com/prometheus/client_golang v1.19.1
        github.com/samber/lo v1.47.0
@@ -33,6 +35,7 @@ require (
        k8s.io/apiextensions-apiserver v0.32.3
        k8s.io/apimachinery v0.32.3
        k8s.io/client-go v0.32.3
+       k8s.io/code-generator v0.32.3
        k8s.io/kubectl v0.30.3
        k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
        sigs.k8s.io/controller-runtime v0.20.4
@@ -91,6 +94,9 @@ require (
        github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // 
indirect
        github.com/cenkalti/backoff/v4 v4.3.0 // indirect
        github.com/cespare/xxhash/v2 v2.3.0 // indirect
+       github.com/clipperhouse/displaywidth v0.3.1 // indirect
+       github.com/clipperhouse/stringish v0.1.1 // indirect
+       github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
        github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
        github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // 
indirect
        github.com/emicklei/go-restful/v3 v3.12.0 // indirect
@@ -139,6 +145,7 @@ require (
        github.com/mailru/easyjson v0.7.7 // indirect
        github.com/mattn/go-colorable v0.1.13 // indirect
        github.com/mattn/go-isatty v0.0.20 // indirect
+       github.com/mattn/go-runewidth v0.0.19 // indirect
        github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 // 
indirect
        github.com/miekg/dns v1.1.65 // indirect
        github.com/mitchellh/copystructure v1.2.0 // indirect
@@ -150,6 +157,9 @@ require (
        github.com/modern-go/reflect2 v1.0.2 // indirect
        github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // 
indirect
        github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // 
indirect
+       github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
+       github.com/olekukonko/errors v1.1.0 // indirect
+       github.com/olekukonko/ll v0.1.2 // indirect
        github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // 
indirect
        github.com/pquerna/otp v1.4.0 // indirect
        github.com/prometheus/client_model v0.6.1 // indirect
@@ -203,6 +213,7 @@ require (
        gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
        k8s.io/apiserver v0.32.3 // indirect
        k8s.io/component-base v0.32.3 // indirect
+       k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 // indirect
        k8s.io/klog/v2 v2.130.1 // indirect
        k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
        moul.io/http2curl/v2 v2.3.0 // indirect
diff --git a/go.sum b/go.sum
index 8904243c..eddf72b5 100644
--- a/go.sum
+++ b/go.sum
@@ -111,6 +111,12 @@ github.com/cenkalti/backoff/v4 v4.3.0 
h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3
 github.com/cenkalti/backoff/v4 v4.3.0/go.mod 
h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
 github.com/cespare/xxhash/v2 v2.3.0 
h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
 github.com/cespare/xxhash/v2 v2.3.0/go.mod 
h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/clipperhouse/displaywidth v0.3.1 
h1:k07iN9gD32177o1y4O1jQMzbLdCrsGJh+blirVYybsk=
+github.com/clipperhouse/displaywidth v0.3.1/go.mod 
h1:tgLJKKyaDOCadywag3agw4snxS5kYEuYR6Y9+qWDDYM=
+github.com/clipperhouse/stringish v0.1.1 
h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
+github.com/clipperhouse/stringish v0.1.1/go.mod 
h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
+github.com/clipperhouse/uax29/v2 v2.2.0 
h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
+github.com/clipperhouse/uax29/v2 v2.2.0/go.mod 
h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod 
h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod 
h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/cpuguy83/go-md2man/v2 v2.0.6 
h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
@@ -285,6 +291,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod 
h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
 github.com/mattn/go-isatty v0.0.16/go.mod 
h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/mattn/go-isatty v0.0.20 
h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 github.com/mattn/go-isatty v0.0.20/go.mod 
h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-runewidth v0.0.19 
h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
+github.com/mattn/go-runewidth v0.0.19/go.mod 
h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
 github.com/mattn/go-zglob v0.0.1/go.mod 
h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
 github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 
h1:ofNAzWCcyTALn2Zv40+8XitdzCgXY6e9qvXwN9W0YXg=
 github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326/go.mod 
h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
@@ -311,12 +319,22 @@ github.com/munnerz/goautoneg 
v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod 
h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f 
h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
 github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod 
h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
+github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 
h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
+github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod 
h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
+github.com/olekukonko/errors v1.1.0 
h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
+github.com/olekukonko/errors v1.1.0/go.mod 
h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
+github.com/olekukonko/ll v0.1.2 h1:lkg/k/9mlsy0SxO5aC+WEpbdT5K83ddnNhAepz7TQc0=
+github.com/olekukonko/ll v0.1.2/go.mod 
h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew=
+github.com/olekukonko/tablewriter v1.1.1 
h1:b3reP6GCfrHwmKkYwNRFh2rxidGHcT6cgxj/sHiDDx0=
+github.com/olekukonko/tablewriter v1.1.1/go.mod 
h1:De/bIcTF+gpBDB3Alv3fEsZA+9unTsSzAg/ZGADCtn4=
 github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
 github.com/onsi/ginkgo v1.10.1/go.mod 
h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo/v2 v2.22.0 
h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
 github.com/onsi/ginkgo/v2 v2.22.0/go.mod 
h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
 github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
 github.com/onsi/gomega v1.36.1/go.mod 
h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
+github.com/panjf2000/ants/v2 v2.11.3 
h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg=
+github.com/panjf2000/ants/v2 v2.11.3/go.mod 
h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
 github.com/pelletier/go-toml/v2 v2.0.1/go.mod 
h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
 github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod 
h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod 
h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@@ -579,8 +597,12 @@ k8s.io/apiserver v0.32.3 
h1:kOw2KBuHOA+wetX1MkmrxgBr648ksz653j26ESuWNY8=
 k8s.io/apiserver v0.32.3/go.mod h1:q1x9B8E/WzShF49wh3ADOh6muSfpmFL0I2t+TG0Zdgc=
 k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU=
 k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY=
+k8s.io/code-generator v0.32.3 h1:31p2TVzC9+hVdSkAFruAk3JY+iSfzrJ83Qij1yZutyw=
+k8s.io/code-generator v0.32.3/go.mod 
h1:+mbiYID5NLsBuqxjQTygKM/DAdKpAjvBzrJd64NU1G8=
 k8s.io/component-base v0.32.3 h1:98WJvvMs3QZ2LYHBzvltFSeJjEx7t5+8s71P7M74u8k=
 k8s.io/component-base v0.32.3/go.mod 
h1:LWi9cR+yPAv7cu2X9rZanTiFKB2kHA+JjmhkKjCZRpI=
+k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 
h1:si3PfKm8dDYxgfbeA6orqrtLkvvIeH8UqffFJDl0bz4=
+k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9/go.mod 
h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
 k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
 k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
 k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f 
h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
diff --git a/internal/adc/cache/store.go b/internal/adc/cache/store.go
index 6c5a1f23..d41188ed 100644
--- a/internal/adc/cache/store.go
+++ b/internal/adc/cache/store.go
@@ -40,7 +40,7 @@ func NewStore(log logr.Logger) *Store {
        return &Store{
                cacheMap:          make(map[string]Cache),
                pluginMetadataMap: make(map[string]adctypes.PluginMetadata),
-               log:               log,
+               log:               log.WithName("store"),
        }
 }
 
diff --git a/internal/adc/client/client.go b/internal/adc/client/client.go
index 8a498b13..2419dde9 100644
--- a/internal/adc/client/client.go
+++ b/internal/adc/client/client.go
@@ -198,6 +198,7 @@ func (c *Client) Sync(ctx context.Context) 
(map[string]types.ADCExecutionErrors,
                if resources == nil {
                        continue
                }
+               c.log.Info("syncing resources for config", "service_number", 
len(resources.Services))
 
                if err := c.sync(ctx, Task{
                        Name: name + "-sync",
diff --git a/internal/controller/status/updater.go 
b/internal/controller/status/updater.go
index d00aec76..e2ef06ed 100644
--- a/internal/controller/status/updater.go
+++ b/internal/controller/status/updater.go
@@ -37,7 +37,7 @@ import (
        pkgmetrics "github.com/apache/apisix-ingress-controller/pkg/metrics"
 )
 
-const UpdateChannelBufferSize = 1000
+const UpdateChannelBufferSize = 10000
 
 type Update struct {
        NamespacedName k8stypes.NamespacedName
@@ -119,7 +119,7 @@ func (u *UpdateHandler) updateStatus(ctx context.Context, 
update Update) error {
 
        newObj.SetUID(obj.GetUID())
 
-       u.log.Info("updating status", "name", update.NamespacedName.Name,
+       u.log.V(1).Info("updating status", "name", update.NamespacedName.Name,
                "namespace", update.NamespacedName.Namespace,
                "kind", types.KindOf(newObj),
        )
@@ -140,11 +140,10 @@ func (u *UpdateHandler) Start(ctx context.Context) error {
                case update := <-u.updateChannel:
                        // Decrement queue length after removing item from queue
                        pkgmetrics.DecStatusQueueLength()
-                       u.log.V(1).Info("received a status update", 
"namespace", update.NamespacedName.Namespace,
+                       u.log.Info("received a status update", "namespace", 
update.NamespacedName.Namespace,
                                "name", update.NamespacedName.Name,
                                "kind", types.KindOf(update.Resource),
                        )
-
                        u.apply(ctx, update)
                }
        }
diff --git a/internal/controller/utils.go b/internal/controller/utils.go
index 869efefc..892d027c 100644
--- a/internal/controller/utils.go
+++ b/internal/controller/utils.go
@@ -1368,7 +1368,7 @@ func ProcessIngressClassParameters(tctx 
*provider.TranslateContext, c client.Cli
                        return err
                }
 
-               log.Info("found GatewayProxy for IngressClass", "ingressClass", 
ingressClass.Name, "gatewayproxy", gatewayProxy.Name)
+               log.V(1).Info("found GatewayProxy for IngressClass", 
"ingressClass", ingressClass.Name, "gatewayproxy", gatewayProxy.Name)
                tctx.GatewayProxies[ingressClassKind] = *gatewayProxy
                tctx.ResourceParentRefs[objKind] = 
append(tctx.ResourceParentRefs[objKind], ingressClassKind)
 
diff --git a/internal/manager/readiness/manager.go 
b/internal/manager/readiness/manager.go
index eca380eb..b140bd83 100644
--- a/internal/manager/readiness/manager.go
+++ b/internal/manager/readiness/manager.go
@@ -125,7 +125,8 @@ func (r *readinessManager) Start(ctx context.Context) error 
{
                                        })
                                }
                                if len(expected) > 0 {
-                                       r.log.V(1).Info("registering readiness 
state", "gvk", gvk, "expected", expected)
+                                       r.log.Info("registering readiness 
state", "gvk", gvk, "registered_count", len(expected))
+                                       r.log.V(1).Info("registered resources 
for readiness", "gvk", gvk, "resources", expected)
                                        r.registerState(gvk, expected)
                                }
                        }
@@ -135,13 +136,12 @@ func (r *readinessManager) Start(ctx context.Context) 
error {
                        r.isReady.Store(true)
                        close(r.done)
                }
+               r.log.Info("readiness manager started")
        })
        return err
 }
 
 func (r *readinessManager) registerState(gvk schema.GroupVersionKind, list 
[]k8stypes.NamespacedName) {
-       r.mu.Lock()
-       defer r.mu.Unlock()
        if _, ok := r.state[gvk]; !ok {
                r.state[gvk] = make(map[k8stypes.NamespacedName]struct{})
        }
@@ -155,9 +155,12 @@ func (r *readinessManager) Done(obj client.Object, nn 
k8stypes.NamespacedName) {
        if r.IsReady() {
                return
        }
+       <-r.started
+
        r.mu.Lock()
        defer r.mu.Unlock()
        gvk := types.GvkOf(obj)
+       r.log.Info("marking resource as done", "gvk", gvk, "name", nn, 
"state_count", len(r.state[gvk]))
        if _, ok := r.state[gvk]; !ok {
                return
        }
@@ -191,7 +194,7 @@ func (r *readinessManager) WaitReady(ctx context.Context, 
timeout time.Duration)
        case <-ctx.Done():
                return false
        case <-time.After(timeout):
-               return true
+               return false
        case <-r.done:
                return true
        }
diff --git a/internal/provider/apisix/provider.go 
b/internal/provider/apisix/provider.go
index d0d8e48a..029675e2 100644
--- a/internal/provider/apisix/provider.go
+++ b/internal/provider/apisix/provider.go
@@ -76,7 +76,9 @@ func New(log logr.Logger, updater status.Updater, readier 
readiness.ReadinessMan
                o.DefaultBackendMode = ProviderTypeAPISIX
        }
 
-       cli, err := adcclient.New(log, o.DefaultBackendMode, o.SyncTimeout)
+       logger := log.WithName("provider")
+
+       cli, err := adcclient.New(logger, o.DefaultBackendMode, o.SyncTimeout)
        if err != nil {
                return nil, err
        }
@@ -88,7 +90,7 @@ func New(log logr.Logger, updater status.Updater, readier 
readiness.ReadinessMan
                updater:    updater,
                readier:    readier,
                syncCh:     make(chan struct{}, 1),
-               log:        log.WithName("provider"),
+               log:        logger,
        }, nil
 }
 
@@ -249,7 +251,10 @@ func (d *apisixProvider) buildConfig(tctx 
*provider.TranslateContext, nnk types.
 }
 
 func (d *apisixProvider) Start(ctx context.Context) error {
+       d.log.Info("starting provider, waiting for readiness")
        d.readier.WaitReady(ctx, 5*time.Minute)
+       d.log.Info("Ready detected, starting sync loop")
+
        initalSyncDelay := d.InitSyncDelay
        if initalSyncDelay > 0 {
                time.AfterFunc(initalSyncDelay, d.syncNotify)
diff --git a/test/benchmark/benchmark_test.go b/test/benchmark/benchmark_test.go
new file mode 100644
index 00000000..f32f8cf7
--- /dev/null
+++ b/test/benchmark/benchmark_test.go
@@ -0,0 +1,430 @@
+// 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 benchmark
+
+import (
+       "bytes"
+       "context"
+       "fmt"
+       "net/http"
+       "os"
+       "time"
+
+       "github.com/api7/gopkg/pkg/log"
+       . "github.com/onsi/ginkgo/v2"
+       . "github.com/onsi/gomega"
+
+       adctypes "github.com/apache/apisix-ingress-controller/api/adc"
+       "github.com/apache/apisix-ingress-controller/test/e2e/framework"
+       "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+var report = &BenchmarkReport{}
+var totalRoutes = 2000
+var totalConsumers = 2000
+
+var _ = BeforeSuite(func() {
+       routes := os.Getenv("BENCHMARK_ROUTES")
+       if routes != "" {
+               _, err := fmt.Sscanf(routes, "%d", &totalRoutes)
+               Expect(err).NotTo(HaveOccurred(), "parsing BENCHMARK_ROUTES")
+       }
+       consumers := os.Getenv("BENCHMARK_CONSUMERS")
+       if consumers != "" {
+               _, err := fmt.Sscanf(consumers, "%d", &totalConsumers)
+               Expect(err).NotTo(HaveOccurred(), "parsing BENCHMARK_CONSUMERS")
+       }
+})
+var _ = AfterSuite(func() {
+       report.PrintTable()
+})
+
+const gatewayProxyYaml = `
+apiVersion: apisix.apache.org/v1alpha1
+kind: GatewayProxy
+metadata:
+  name: apisix-proxy-config
+spec:
+  provider:
+    type: ControlPlane
+    controlPlane:
+      service:
+        name: %s
+        port: 9180
+      auth:
+        type: AdminKey
+        adminKey:
+          value: "%s"
+`
+
+var _ = Describe("Benchmark Test", func() {
+       var (
+               s                = scaffold.NewDefaultScaffold()
+               controlAPIClient scaffold.ControlAPIClient
+       )
+
+       BeforeEach(func() {
+               By("port-forward to control api service")
+               var err error
+               controlAPIClient, err = s.ControlAPIClient()
+               Expect(err).NotTo(HaveOccurred(), "create control api client")
+       })
+
+       Context("Benchmark ApisixRoute", func() {
+               const ingressClassYaml = `
+apiVersion: networking.k8s.io/v1
+kind: IngressClass
+metadata:
+  name: apisix
+spec:
+  controller: "%s"
+  parameters:
+    apiGroup: "apisix.apache.org"
+    kind: "GatewayProxy"
+    name: "apisix-proxy-config"
+    namespace: %s
+    scope: "Namespace"
+`
+
+               const apisixRouteSpec = `
+apiVersion: apisix.apache.org/v2
+kind: ApisixRoute
+metadata:
+  name: %s
+spec:
+  ingressClassName: apisix
+  http:
+  - name: rule0
+    match:
+      paths:
+      - /get
+      exprs:
+      - subject:
+          scope: Header
+          name: X-Route-Name
+        op: Equal
+        value: %s
+    backends:
+    - serviceName: httpbin-service-e2e-test
+      servicePort: 80
+`
+               var apisixRouteSpecHeaders = `
+apiVersion: apisix.apache.org/v2
+kind: ApisixRoute
+metadata:
+  name: %s
+spec:
+  ingressClassName: apisix
+  http:
+  - name: rule0
+    match:
+      paths:
+      - /headers
+      exprs:
+      - subject:
+          scope: Header
+          name: X-Route-Name
+        op: Equal
+        value: %s
+    backends:
+    - serviceName: httpbin-service-e2e-test
+      servicePort: 80
+`
+
+               var apisixUpstreamSpec = `
+apiVersion: apisix.apache.org/v2
+kind: ApisixUpstream
+metadata:
+  name: httpbin-service-e2e-test
+spec:
+  ingressClassName: apisix
+  scheme: https
+`
+               var apisixRouteSpecKeyAuth = `
+apiVersion: apisix.apache.org/v2
+kind: ApisixRoute
+metadata:
+  name: key-auth
+spec:
+  ingressClassName: apisix
+  http:
+  - name: rule0
+    match:
+      paths:
+      - /get
+    backends:
+    - serviceName: httpbin-service-e2e-test
+      servicePort: 80
+    authentication:
+      enable: true
+      type: keyAuth
+`
+               var keyAuth = `
+apiVersion: apisix.apache.org/v2
+kind: ApisixConsumer
+metadata:
+  name: %s
+spec:
+  ingressClassName: apisix
+  authParameter:
+    keyAuth:
+      value:
+        key: %s
+`
+
+               getRouteName := func(i int) string {
+                       return fmt.Sprintf("test-route-%04d", i)
+               }
+
+               createBatchApisixRoutes := func(number int) string {
+                       var buf bytes.Buffer
+                       for i := 0; i < number; i++ {
+                               name := getRouteName(i)
+                               fmt.Fprintf(&buf, apisixRouteSpec, name, name)
+                               buf.WriteString("\n---\n")
+                       }
+                       return buf.String()
+               }
+               getConsumerName := func(i int) string {
+                       return fmt.Sprintf("consumer-%04d", i)
+               }
+               createBatchConsumers := func(number int) string {
+                       var buf bytes.Buffer
+                       for i := 0; i < number; i++ {
+                               name := getConsumerName(i)
+                               fmt.Fprintf(&buf, keyAuth, name, name)
+                               buf.WriteString("\n---\n")
+                       }
+                       return buf.String()
+               }
+
+               benchmark := func(scenario string) {
+                       s.Deployer.ScaleIngress(0)
+                       By(fmt.Sprintf("prepare %d ApisixRoutes", totalRoutes))
+                       err := 
s.CreateResourceFromString(createBatchApisixRoutes(totalRoutes))
+                       Expect(err).NotTo(HaveOccurred(), "creating 
ApisixRoutes")
+                       s.Deployer.ScaleIngress(1)
+
+                       now := time.Now()
+                       By(fmt.Sprintf("start cale time for applying %d 
ApisixRoutes to take effect", totalRoutes))
+                       err = s.EnsureNumService(controlAPIClient, func(actual 
int) bool { return actual == totalRoutes })
+                       Expect(err).ShouldNot(HaveOccurred())
+                       costTime := time.Since(now)
+                       report.Add(scenario, fmt.Sprintf("Apply %d 
ApisixRoutes", totalRoutes), costTime)
+
+                       By("Test the time required for an ApisixRoute update to 
take effect")
+                       name := getRouteName(int(time.Now().Unix()))
+                       err = 
s.CreateResourceFromString(fmt.Sprintf(apisixRouteSpecHeaders, name, name))
+                       Expect(err).NotTo(HaveOccurred())
+                       now = time.Now()
+                       Eventually(func() int {
+                               return 
s.NewAPISIXClient().GET("/headers").WithHeader("X-Route-Name", 
name).Expect().Raw().StatusCode
+                       }).WithTimeout(15 * time.Minute).ProbeEvery(100 * 
time.Millisecond).Should(Equal(http.StatusOK))
+                       report.Add(scenario, fmt.Sprintf("Update a single 
ApisixRoute base on %d ApisixRoutes", totalRoutes), time.Since(now))
+
+                       By("Test the time required for a service endpoint 
change to take effect")
+                       err = s.ScaleHTTPBIN(2)
+                       Expect(err).NotTo(HaveOccurred(), "scale httpbin 
deployment")
+                       now = time.Now()
+                       err = s.EnsureNumUpstreamNodes(controlAPIClient, "", 2)
+                       Expect(err).ShouldNot(HaveOccurred())
+                       costTime = time.Since(now)
+                       report.Add(scenario, fmt.Sprintf("Service endpoint 
change base on %d ApisixRoutes", totalRoutes), costTime)
+
+                       By("Test the time required for an ApisixUpstream update 
to take effect")
+                       err = s.CreateResourceFromString(apisixUpstreamSpec)
+                       Expect(err).NotTo(HaveOccurred(), "creating 
ApisixUpstream")
+                       now = time.Now()
+                       err = s.ExpectUpstream(controlAPIClient, "", 
func(upstream adctypes.Upstream) bool {
+                               if upstream.Scheme != "https" {
+                                       log.Warnf("expect upstream: [%s] scheme 
to be https, but got [%s]", upstream.Name, upstream.Scheme)
+                                       return false
+                               }
+                               return true
+                       })
+                       Expect(err).ShouldNot(HaveOccurred())
+                       costTime = time.Since(now)
+                       report.Add(scenario, fmt.Sprintf("Update ApisixUpstream 
base on %d ApisixRoutes", totalRoutes), costTime)
+               }
+
+               BeforeEach(func() {
+                       By("create GatewayProxy")
+                       gatewayProxy := fmt.Sprintf(gatewayProxyYaml, 
framework.ProviderType, s.AdminKey())
+                       err := 
s.CreateResourceFromStringWithNamespace(gatewayProxy, s.Namespace())
+                       Expect(err).NotTo(HaveOccurred(), "creating 
GatewayProxy")
+                       time.Sleep(5 * time.Second)
+
+                       By("create IngressClass")
+                       err = 
s.CreateResourceFromStringWithNamespace(fmt.Sprintf(ingressClassYaml, 
s.GetControllerName(), s.Namespace()), "")
+                       Expect(err).NotTo(HaveOccurred(), "creating 
IngressClass")
+                       time.Sleep(5 * time.Second)
+               })
+               It("benchmark ApisixRoute", func() {
+                       benchmark("ApisixRoute Benchmark")
+               })
+               It("10 apisix-standalone pod scale benchmark", func() {
+                       if framework.ProviderType != 
framework.ProviderTypeAPISIXStandalone {
+                               Skip("only apisix-standalone support scale 
benchmark")
+                       }
+                       s.Deployer.ScaleDataplane(10)
+                       benchmark("ApisixRoute Benchmark with 10 
apisix-standalone pods")
+               })
+               It("ApisixRoute With Consumers benchmark", func() {
+                       s.Deployer.ScaleIngress(0)
+                       By(fmt.Sprintf("prepare %d ApisixConsumers", 
totalRoutes))
+                       err := 
s.CreateResourceFromString(createBatchConsumers(totalRoutes))
+                       Expect(err).NotTo(HaveOccurred(), "creating 
ApisixConsumers")
+                       err = s.CreateResourceFromString(apisixRouteSpecKeyAuth)
+                       Expect(err).NotTo(HaveOccurred(), "creating ApisixRoute 
with KeyAuth")
+                       s.Deployer.ScaleIngress(1)
+
+                       now := time.Now()
+                       Eventually(func() error {
+                               consumer, err := 
s.DefaultDataplaneResource().Consumer().List(context.Background())
+                               if err != nil {
+                                       return err
+                               }
+                               if len(consumer) != totalConsumers {
+                                       return fmt.Errorf("expect %d consumers, 
but got %d", totalConsumers, len(consumer))
+                               }
+                               return nil
+                       
}).WithTimeout(15*time.Minute).ProbeEvery(1*time.Second).ShouldNot(HaveOccurred(),
 "waiting for all consumers to be synced to APISIX")
+                       costTime := time.Since(now)
+                       report.AddResult(TestResult{
+                               Scenario:         "ApisixRoute With Consumers 
Benchmark",
+                               CaseName:         fmt.Sprintf("Apply %d 
ApisixConsumers and ApisixRoute with KeyAuth", totalConsumers),
+                               CostTime:         costTime,
+                               IsRequestGateway: true,
+                       })
+               })
+       })
+
+       Context("Benchmark HTTPRoute", func() {
+               const httpRouteSpec = `
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: %s
+spec:
+  parentRefs:
+  - name: %s
+  rules:
+  - matches: 
+    - path:
+        type: Exact
+        value: /get
+      headers:
+        - type: Exact
+          name: X-Route-Name
+          value: %s
+    # name: get
+    backendRefs:
+    - name: httpbin-service-e2e-test
+      port: 80
+`
+               const httpRouteSpecHeaders = `
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: %s
+spec:
+  parentRefs:
+  - name: %s
+  rules:
+  - matches: 
+    - path:
+        type: Exact
+        value: /headers
+      headers:
+        - type: Exact
+          name: X-Route-Name
+          value: %s
+    # name: get
+    backendRefs:
+    - name: httpbin-service-e2e-test
+      port: 80
+`
+
+               createBatchHTTPRoutes := func(number int, parentGateway string) 
string {
+                       var buf bytes.Buffer
+                       for i := 0; i < number; i++ {
+                               name := getRouteName(i)
+                               fmt.Fprintf(&buf, httpRouteSpec, name, 
parentGateway, name)
+                               buf.WriteString("\n---\n")
+                       }
+                       return buf.String()
+               }
+
+               BeforeEach(func() {
+                       By("create GatewayProxy")
+                       gatewayProxy := fmt.Sprintf(gatewayProxyYaml, 
framework.ProviderType, s.AdminKey())
+                       err := 
s.CreateResourceFromStringWithNamespace(gatewayProxy, s.Namespace())
+                       Expect(err).NotTo(HaveOccurred(), "creating 
GatewayProxy")
+                       time.Sleep(5 * time.Second)
+
+                       By("create GatewayClass")
+                       
Expect(s.CreateResourceFromString(s.GetGatewayClassYaml())).NotTo(HaveOccurred(),
 "creating GatewayClass")
+
+                       By("create Gateway")
+                       
Expect(s.CreateResourceFromString(s.GetGatewayYaml())).NotTo(HaveOccurred(), 
"creating Gateway")
+                       time.Sleep(5 * time.Second)
+               })
+
+               It("benchmark HTTPRoute", func() {
+                       s.Deployer.ScaleIngress(0)
+                       By(fmt.Sprintf("prepare %d HTTPRoute", totalRoutes))
+                       err := 
s.CreateResourceFromString(createBatchHTTPRoutes(totalRoutes, s.Namespace()))
+                       Expect(err).NotTo(HaveOccurred(), "creating HTTPRoute")
+                       s.Deployer.ScaleIngress(1)
+
+                       now := time.Now()
+                       By(fmt.Sprintf("start cale time for applying %d 
HTTPRoute to take effect", totalRoutes))
+                       err = s.EnsureNumService(controlAPIClient, func(actual 
int) bool { return actual == totalRoutes })
+                       Expect(err).ShouldNot(HaveOccurred())
+                       costTime := time.Since(now)
+                       report.Add("HTTPRoute Benchmark", fmt.Sprintf("Apply %d 
HTTPRoute", totalRoutes), costTime)
+
+                       By("Test the time required for an HTTPRoute update to 
take effect")
+                       name := getRouteName(int(time.Now().Unix()))
+                       err = 
s.CreateResourceFromString(fmt.Sprintf(httpRouteSpecHeaders, name, 
s.Namespace(), name))
+                       Expect(err).NotTo(HaveOccurred())
+                       now = time.Now()
+                       Eventually(func() int {
+                               return 
s.NewAPISIXClient().GET("/headers").WithHeader("X-Route-Name", 
name).Expect().Raw().StatusCode
+                       }).WithTimeout(5 * time.Minute).ProbeEvery(100 * 
time.Millisecond).Should(Equal(http.StatusOK))
+                       report.AddResult(TestResult{
+                               Scenario: "HTTPRoute Benchmark",
+                               CaseName: fmt.Sprintf("Update a single 
HTTPRoute base on %d HTTPRoute", totalRoutes),
+                               CostTime: time.Since(now),
+                       })
+
+                       By("Test the time required for a service endpoint 
change to take effect")
+                       err = s.ScaleHTTPBIN(2)
+                       Expect(err).NotTo(HaveOccurred(), "scale httpbin 
deployment")
+                       now = time.Now()
+                       err = s.EnsureNumUpstreamNodes(controlAPIClient, "", 2)
+                       Expect(err).ShouldNot(HaveOccurred())
+                       costTime = time.Since(now)
+                       report.Add("HTTPRoute Benchmark", fmt.Sprintf("Service 
endpoint change base on %d HTTPRoute", totalRoutes), costTime)
+               })
+       })
+})
+
+func getRouteName(i int) string {
+       return fmt.Sprintf("test-route-%04d", i)
+}
diff --git a/test/benchmark/suite_test.go b/test/benchmark/suite_test.go
new file mode 100644
index 00000000..32f60af8
--- /dev/null
+++ b/test/benchmark/suite_test.go
@@ -0,0 +1,41 @@
+// 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 benchmark
+
+import (
+       "fmt"
+       "testing"
+
+       . "github.com/onsi/ginkgo/v2"
+       . "github.com/onsi/gomega"
+
+       "github.com/apache/apisix-ingress-controller/test/e2e/framework"
+       "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+// Run long-term-stability tests using Ginkgo runner.
+func TestBenchmark(t *testing.T) {
+       RegisterFailHandler(Fail)
+       var f = framework.NewFramework()
+       _ = f
+
+       scaffold.NewDeployer = scaffold.NewAPISIXDeployer
+
+       _, _ = fmt.Fprintf(GinkgoWriter, "Starting Benchmark Tests\n")
+       RunSpecs(t, "Benchmark Tests Suite")
+}
diff --git a/test/benchmark/utis.go b/test/benchmark/utis.go
new file mode 100644
index 00000000..35de0b90
--- /dev/null
+++ b/test/benchmark/utis.go
@@ -0,0 +1,76 @@
+// 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 benchmark
+
+import (
+       "encoding/json"
+       "fmt"
+       "os"
+       "time"
+
+       "github.com/api7/gopkg/pkg/log"
+       "github.com/olekukonko/tablewriter"
+       "go.uber.org/zap"
+)
+
+type TestResult struct {
+       Scenario         string        `json:"scenario"`
+       CaseName         string        `json:"case_name"`
+       CostTime         time.Duration `json:"cost_time"`
+       IsRequestGateway bool          `json:"is_request_gateway,omitempty"`
+}
+
+type BenchmarkReport struct {
+       Results []TestResult
+}
+
+func (r *BenchmarkReport) PrintTable() {
+       table := tablewriter.NewWriter(os.Stdout)
+       table.Header([]string{"Scenario", "Case", "Cost", "IsRequestGateway"})
+
+       for _, res := range r.Results {
+               if err := table.Append([]any{
+                       res.Scenario,
+                       res.CaseName,
+                       res.CostTime.String(),
+                       res.IsRequestGateway,
+               }); err != nil {
+                       log.Errorw("failed to append row to table", 
zap.Error(err))
+               }
+       }
+       if err := table.Render(); err != nil {
+               log.Errorw("failed to render table", zap.Error(err))
+       }
+}
+
+func (r *BenchmarkReport) PrintJSON() {
+       b, _ := json.MarshalIndent(r.Results, "", "  ")
+       fmt.Println(string(b))
+}
+
+func (r *BenchmarkReport) AddResult(result TestResult) {
+       r.Results = append(r.Results, result)
+}
+
+func (r *BenchmarkReport) Add(scenario, caseName string, cost time.Duration) {
+       r.Results = append(r.Results, TestResult{
+               Scenario: scenario,
+               CaseName: caseName,
+               CostTime: cost,
+       })
+}
diff --git a/test/e2e/framework/manifests/apisix-standalone.yaml 
b/test/e2e/framework/manifests/apisix-standalone.yaml
deleted file mode 100644
index 0eda2bc8..00000000
--- a/test/e2e/framework/manifests/apisix-standalone.yaml
+++ /dev/null
@@ -1,153 +0,0 @@
-# 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.
-
-apiVersion: v1
-kind: ConfigMap
-metadata:
-  name: apisix-conf
-data:
-  config.yaml: |
-    deployment:
-      role: traditional
-      role_traditional:
-          config_provider: yaml
-      admin:
-        allow_admin:
-        - 0.0.0.0/0
-        admin_key:
-        - key: {{ .AdminKey }}
-          name: admin
-          role: admin
-    nginx_config:
-      worker_processes: 2
-      error_log_level: info
-    apisix:
-      proxy_mode: http&stream      
-      stream_proxy:                 # TCP/UDP proxy
-        tcp:                        # TCP proxy port list
-          - 9100
-          - addr: 9110
-            tls: true
-        udp:                        # UDP proxy port list
-          - 9200
-    discovery:
-      dns:
-        servers:
-        - "10.96.0.10:53"        # use the real address of your dns server.
-                                 # currently we use KIND as the standard test 
environment, so here we can hard-code the default DNS address first.
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  name: apisix
-  labels:
-    app.kubernetes.io/name: apisix
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      app.kubernetes.io/name: apisix
-  template:
-    metadata:
-      labels:
-        app.kubernetes.io/name: apisix
-    spec:
-      initContainers:
-        - name: config-setup
-          image: apache/apisix:dev
-          command:
-            - sh
-            - -c
-            - |
-              echo "Copying default config directory to writable volume"
-              cp -r /usr/local/apisix/conf/* /tmp/apisix-conf/
-              echo "Overwriting config.yaml with custom configuration"
-              cp /tmp/config-source/config.yaml /tmp/apisix-conf/config.yaml
-              echo "Config setup completed successfully"
-              ls -la /tmp/apisix-conf/
-          volumeMounts:
-            - name: config-source
-              mountPath: /tmp/config-source
-            - name: config-writable
-              mountPath: /tmp/apisix-conf
-      containers:
-        - name: apisix
-          image: apache/apisix:dev
-          ports:
-            - name: http
-              containerPort: 9080
-              protocol: TCP
-            - name: https
-              containerPort: 9443
-              protocol: TCP
-            - name: admin
-              containerPort: 9180
-              protocol: TCP
-            - name: tcp
-              containerPort: 9100
-              protocol: TCP
-            - name: udp
-              containerPort: 9200
-              protocol: UDP
-            - name: tls
-              containerPort: 9110
-              protocol: TCP
-          volumeMounts:
-            - name: config-writable
-              mountPath: /usr/local/apisix/conf
-      volumes:
-        - name: config-source
-          configMap:
-            name: apisix-conf
-        - name: config-writable
-          emptyDir: {}
----
-apiVersion: v1
-kind: Service
-metadata:
-  name: {{ .ServiceName }}
-  labels:
-    app.kubernetes.io/name: apisix
-spec:
-  ports:
-    - port: {{ .ServiceHTTPPort }}
-      name: http
-      protocol: TCP
-      targetPort: 9080
-    - port: {{ .ServiceHTTPSPort }}
-      name: https
-      protocol: TCP
-      targetPort: 9443
-    - port: 9180
-      name: admin
-      protocol: TCP
-      targetPort: 9180
-    - name: tcp
-      port: 9100
-      protocol: TCP
-      targetPort: 9100
-    - name: udp
-      port: 9200
-      protocol: UDP
-      targetPort: 9200
-    - name: tls
-      port: 9110
-      protocol: TCP
-      targetPort: 9110
-  selector:
-    app.kubernetes.io/name: apisix 
-  type: {{ .ServiceType | default "NodePort" }}
diff --git a/test/e2e/framework/manifests/apisix.yaml 
b/test/e2e/framework/manifests/apisix.yaml
index 31581bcc..eada5041 100644
--- a/test/e2e/framework/manifests/apisix.yaml
+++ b/test/e2e/framework/manifests/apisix.yaml
@@ -42,6 +42,9 @@ data:
     nginx_config:
       worker_processes: 2
       error_log_level: info
+      meta:
+        lua_shared_dict:
+          standalone-config: 50m
     apisix:
       proxy_mode: http&stream      
       stream_proxy:                 # TCP/UDP proxy
@@ -168,3 +171,20 @@ spec:
   selector:
     app.kubernetes.io/name: apisix 
   type: {{ .ServiceType | default "NodePort" }}
+
+---
+
+apiVersion: v1
+kind: Service
+metadata:
+  name: apisix-control-api
+  labels:
+    app.kubernetes.io/name: apisix-control-api
+spec:
+  ports:
+    - port: 9090
+      name: control
+      protocol: TCP
+      targetPort: 9090
+  selector:
+    app.kubernetes.io/name: apisix
diff --git a/test/e2e/scaffold/scaffold.go b/test/e2e/scaffold/scaffold.go
index c23acd6a..799cc798 100644
--- a/test/e2e/scaffold/scaffold.go
+++ b/test/e2e/scaffold/scaffold.go
@@ -20,11 +20,13 @@ package scaffold
 import (
        "context"
        "crypto/tls"
+       "encoding/json"
        "fmt"
        "net/http"
        "net/url"
        "os"
        "strings"
+       "time"
 
        "github.com/api7/gopkg/pkg/log"
        "github.com/gavv/httpexpect/v2"
@@ -32,10 +34,13 @@ import (
        "github.com/gruntwork-io/terratest/modules/testing"
        . "github.com/onsi/ginkgo/v2" //nolint:staticcheck
        . "github.com/onsi/gomega"    //nolint:staticcheck
+       "go.uber.org/zap"
        corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/apimachinery/pkg/util/wait"
        "sigs.k8s.io/controller-runtime/pkg/client"
 
+       adctypes "github.com/apache/apisix-ingress-controller/api/adc"
        apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
        "github.com/apache/apisix-ingress-controller/test/e2e/framework"
 )
@@ -556,3 +561,104 @@ func (s *Scaffold) GetMetricsEndpoint() string {
        s.addFinalizers(tunnel.Close)
        return fmt.Sprintf("http://%s/metrics";, tunnel.Endpoint())
 }
+
+func (s *Scaffold) ControlAPIClient() (ControlAPIClient, error) {
+       tunnel := k8s.NewTunnel(s.kubectlOptions, k8s.ResourceTypeService, 
"apisix-control-api", 9090, 9090)
+       if err := tunnel.ForwardPortE(s.t); err != nil {
+               return nil, err
+       }
+       s.addFinalizers(tunnel.Close)
+
+       return &controlAPI{
+               client: NewClient("http", tunnel.Endpoint()),
+       }, nil
+}
+
+func (s *Scaffold) EnsureNumService(controlAPIClient ControlAPIClient, matcher 
func(result int) bool) error {
+       times := 0
+       return wait.PollUntilContextTimeout(context.Background(), 
100*time.Millisecond, 10*time.Minute, true, func(ctx context.Context) (done 
bool, err error) {
+               times++
+               results, _, err := controlAPIClient.ListServices()
+               if err != nil {
+                       log.Errorw("failed to ListServices", zap.Error(err))
+                       return false, nil
+               }
+               if !matcher(len(results)) {
+                       log.Debugw("number of effective services", 
zap.Int("number", len(results)), zap.Int("times", times))
+                       return false, nil
+               }
+               return true, nil
+       })
+}
+
+func (s *Scaffold) ExpectUpstream(controlAPIClient ControlAPIClient, name 
string, matcher func(upstream adctypes.Upstream) bool) error {
+       times := 0
+       return wait.PollUntilContextTimeout(context.Background(), 
1*time.Second, 10*time.Minute, true, func(ctx context.Context) (done bool, err 
error) {
+               times++
+               upstreams, _, err := controlAPIClient.ListUpstreams()
+               if err != nil {
+                       log.Errorw("failed to ListServices", zap.Error(err))
+                       return false, nil
+               }
+               for _, upstream := range upstreams {
+                       upsValue := upstream.(map[string]any)
+                       data, err := json.Marshal(upsValue["value"])
+                       if err != nil {
+                               return false, fmt.Errorf("failed to marshal 
upstream: %v", err)
+                       }
+
+                       var ups adctypes.Upstream
+                       if err := json.Unmarshal(data, &ups); err != nil {
+                               return false, fmt.Errorf("failed to unmarshal 
upstream: %v", err)
+                       }
+                       if name != "" && ups.Name != name {
+                               continue
+                       }
+                       if ok := matcher(ups); !ok {
+                               return false, nil
+                       }
+               }
+               return true, nil
+       })
+}
+
+func (s *Scaffold) EnsureNumUpstreamNodes(controlAPIClient ControlAPIClient, 
name string, number int) error {
+       return s.ExpectUpstream(controlAPIClient, name, func(upstream 
adctypes.Upstream) bool {
+               if len(upstream.Nodes) != number {
+                       log.Warnf("expect upstream: [%s] nodes num to be %d, 
but got %d", upstream.Name, number, len(upstream.Nodes))
+                       return false
+               }
+               return true
+       })
+}
+
+type ControlAPIClient interface {
+       ListServices() ([]any, int64, error)
+       ListUpstreams() ([]any, int64, error)
+}
+
+type controlAPI struct {
+       client *httpexpect.Expect
+}
+
+func (c *controlAPI) ListUpstreams() (result []any, total int64, err error) {
+       resp := c.client.Request(http.MethodGet, "/v1/upstreams").Expect()
+       if resp.Raw().StatusCode != http.StatusOK {
+               return nil, 0, fmt.Errorf("unexpected status code: %v, message: 
%s", resp.Raw().StatusCode, resp.Body().Raw())
+       }
+       if err = json.Unmarshal([]byte(resp.Body().Raw()), &result); err != nil 
{
+               return nil, 0, fmt.Errorf("failed to unmarshal response body: 
%w", err)
+       }
+       return result, int64(len(result)), err
+}
+
+func (c *controlAPI) ListServices() (result []any, total int64, err error) {
+       resp := c.client.Request(http.MethodGet, "/v1/services").Expect()
+       if resp.Raw().StatusCode != http.StatusOK {
+               return nil, 0, fmt.Errorf("unexpected status code: %v, message: 
%s", resp.Raw().StatusCode, resp.Body().Raw())
+       }
+       if err = json.Unmarshal([]byte(resp.Body().Raw()), &result); err != nil 
{
+               return nil, 0, fmt.Errorf("failed to unmarshal response body: 
%w", err)
+       }
+       return result, int64(len(result)), err
+}


Reply via email to