This is an automated email from the ASF dual-hosted git repository. ronething pushed a commit to branch test/load-test in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git
commit 55959246ffa9cd6071cf3b16b64152c41926748c Author: ashing <axing...@gmail.com> AuthorDate: Wed Jul 16 19:42:48 2025 +0800 test: load test Signed-off-by: ashing <axing...@gmail.com> --- Makefile | 4 + go.mod | 29 ++-- go.sum | 57 ++++---- internal/controller/status/updater.go | 3 +- internal/manager/run.go | 1 + internal/provider/adc/adc.go | 28 +++- pkg/metrics/metrics.go | 89 ++++++++++++ test/e2e/framework/apisix_consts.go | 3 +- test/e2e/framework/manifests/apisix.yaml | 18 +++ test/e2e/framework/manifests/ingress.yaml | 19 ++- test/e2e/load-test/e2e_test.go | 43 ++++++ test/e2e/load-test/spec_subject.go | 226 ++++++++++++++++++++++++++++++ test/e2e/scaffold/apisix_deployer.go | 75 +++++----- test/e2e/scaffold/scaffold.go | 33 +++++ 14 files changed, 541 insertions(+), 87 deletions(-) diff --git a/Makefile b/Makefile index 15b7ecf1..bf23f2bd 100644 --- a/Makefile +++ b/Makefile @@ -135,6 +135,10 @@ conformance-test: conformance-test-standalone: go test -v ./test/conformance/apisix -tags=conformance -timeout 60m +.PHONY: load-test +load-test: + go test -v ./test/e2e/load-test -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 068e83a9..de4b946c 100644 --- a/go.mod +++ b/go.mod @@ -17,9 +17,10 @@ require ( github.com/onsi/ginkgo/v2 v2.20.0 github.com/onsi/gomega v1.34.1 github.com/pkg/errors v0.9.1 + github.com/prometheus/client_golang v1.19.1 github.com/samber/lo v1.47.0 github.com/spf13/cobra v1.8.1 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 gopkg.in/yaml.v3 v3.0.1 @@ -108,10 +109,10 @@ require ( github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pquerna/otp v1.2.0 // indirect - github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sanity-io/litter v1.5.5 // indirect github.com/sergi/go-diff v1.3.1 // indirect @@ -129,31 +130,33 @@ require ( github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect github.com/yudai/gojsondiff v1.0.0 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/sdk v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.6.0 // indirect golang.org/x/crypto v0.36.0 // indirect - golang.org/x/mod v0.20.0 // indirect + golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.38.0 // indirect - golang.org/x/oauth2 v0.21.0 // indirect + golang.org/x/oauth2 v0.25.0 // indirect golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/term v0.30.0 // indirect golang.org/x/text v0.23.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.24.0 // indirect + golang.org/x/tools v0.26.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect - google.golang.org/grpc v1.66.2 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect + google.golang.org/grpc v1.71.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect diff --git a/go.sum b/go.sum index 2be83ffa..f4b40c36 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -255,8 +256,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -298,8 +299,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= @@ -331,20 +332,24 @@ github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -374,8 +379,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -389,8 +394,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -440,24 +445,24 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= -google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= -google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= -google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= +google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/controller/status/updater.go b/internal/controller/status/updater.go index 47ac41ee..00b03e01 100644 --- a/internal/controller/status/updater.go +++ b/internal/controller/status/updater.go @@ -28,7 +28,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -const UpdateChannelBufferSize = 1000 +const UpdateChannelBufferSize = 5000 type Update struct { NamespacedName types.NamespacedName @@ -136,5 +136,6 @@ type UpdateWriter struct { func (u *UpdateWriter) Update(update Update) { u.wg.Wait() + // len(u.updateChannel) u.updateChannel <- update } diff --git a/internal/manager/run.go b/internal/manager/run.go index b48746c1..a27b2732 100644 --- a/internal/manager/run.go +++ b/internal/manager/run.go @@ -42,6 +42,7 @@ import ( "github.com/apache/apisix-ingress-controller/internal/controller/config" "github.com/apache/apisix-ingress-controller/internal/controller/status" "github.com/apache/apisix-ingress-controller/internal/provider/adc" + _ "github.com/apache/apisix-ingress-controller/pkg/metrics" ) var ( diff --git a/internal/provider/adc/adc.go b/internal/provider/adc/adc.go index baed0d7f..491444a2 100644 --- a/internal/provider/adc/adc.go +++ b/internal/provider/adc/adc.go @@ -42,6 +42,7 @@ import ( "github.com/apache/apisix-ingress-controller/internal/provider/adc/translator" "github.com/apache/apisix-ingress-controller/internal/types" "github.com/apache/apisix-ingress-controller/internal/utils" + pkgmetrics "github.com/apache/apisix-ingress-controller/pkg/metrics" ) type adcConfig struct { @@ -311,9 +312,13 @@ func (d *adcClient) Start(ctx context.Context) error { for { select { case <-ticker.C: + start := time.Now() + log.Infof("adcClient start sync, %v", start) if err := d.Sync(ctx); err != nil { log.Error(err) } + duration := time.Since(start) + log.Infof("adcClient sync duration: %v, now: %v", duration, time.Now()) case <-ctx.Done(): return nil } @@ -390,14 +395,35 @@ func (d *adcClient) sync(ctx context.Context, task Task) error { var errs types.ADCExecutionErrors for _, config := range task.configs { - if err := d.executor.Execute(ctx, d.BackendMode, config, args); err != nil { + // Record sync duration for each config + startTime := time.Now() + resourceType := strings.Join(task.ResourceTypes, ",") + if resourceType == "" { + resourceType = "all" + } + + err := d.executor.Execute(ctx, d.BackendMode, config, args) + duration := time.Since(startTime).Seconds() + log.Infof("synced %s in %f seconds, service list length: %d", config.Name, duration, len(task.Resources.Services)) + + status := "success" + if err != nil { + status = "failure" log.Errorw("failed to execute adc command", zap.Error(err), zap.Any("config", config)) + var execErr types.ADCExecutionError if errors.As(err, &execErr) { errs.Errors = append(errs.Errors, execErr) + pkgmetrics.RecordExecutionError(config.Name, execErr.Name) + } else { + pkgmetrics.RecordExecutionError(config.Name, "unknown") } } + + // Record metrics + pkgmetrics.RecordSyncDuration(config.Name, resourceType, status, duration) } + if len(errs.Errors) > 0 { return errs } diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go new file mode 100644 index 00000000..db9decdc --- /dev/null +++ b/pkg/metrics/metrics.go @@ -0,0 +1,89 @@ +// 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 metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + "sigs.k8s.io/controller-runtime/pkg/metrics" +) + +var ( + // ADC sync operation duration histogram + ADCSyncDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "apisix_ingress_adc_sync_duration_seconds", + Help: "Time spent on ADC sync operations", + Buckets: prometheus.DefBuckets, + }, + []string{"config_name", "resource_type", "status"}, + ) + + // ADC sync operation counter + ADCSyncTotal = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "apisix_ingress_adc_sync_total", + Help: "Total number of ADC sync operations", + }, + []string{"config_name", "resource_type", "status"}, + ) + + // ADC execution errors counter + ADCExecutionErrors = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "apisix_ingress_adc_execution_errors_total", + Help: "Total number of ADC execution errors", + }, + []string{"config_name", "error_type"}, + ) + + // Resource sync gauge + ResourceSyncGauge = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "apisix_ingress_resources_synced", + Help: "Number of resources currently synced", + }, + []string{"config_name", "resource_type"}, + ) +) + +// init registers all metrics with the global prometheus registry +func init() { + // Register metrics with controller-runtime's metrics registry + metrics.Registry.MustRegister( + ADCSyncDuration, + ADCSyncTotal, + ADCExecutionErrors, + ResourceSyncGauge, + ) +} + +// RecordSyncDuration records the duration of an ADC sync operation +func RecordSyncDuration(configName, resourceType, status string, duration float64) { + ADCSyncDuration.WithLabelValues(configName, resourceType, status).Observe(duration) + ADCSyncTotal.WithLabelValues(configName, resourceType, status).Inc() +} + +// RecordExecutionError records an ADC execution error +func RecordExecutionError(configName, errorType string) { + ADCExecutionErrors.WithLabelValues(configName, errorType).Inc() +} + +// UpdateResourceGauge updates the resource sync gauge +func UpdateResourceGauge(configName, resourceType string, count float64) { + ResourceSyncGauge.WithLabelValues(configName, resourceType).Set(count) +} diff --git a/test/e2e/framework/apisix_consts.go b/test/e2e/framework/apisix_consts.go index 42214982..cf145b8c 100644 --- a/test/e2e/framework/apisix_consts.go +++ b/test/e2e/framework/apisix_consts.go @@ -27,7 +27,8 @@ import ( ) var ( - ProviderType = cmp.Or(os.Getenv("PROVIDER_TYPE"), "apisix") + ProviderType = cmp.Or(os.Getenv("PROVIDER_TYPE"), "apisix-standalone") + ProviderSyncPeriod = cmp.Or(os.Getenv("PROVIDER_SYNC_PERIOD"), "5s") ) var ( diff --git a/test/e2e/framework/manifests/apisix.yaml b/test/e2e/framework/manifests/apisix.yaml index affa4bfb..568bde3d 100644 --- a/test/e2e/framework/manifests/apisix.yaml +++ b/test/e2e/framework/manifests/apisix.yaml @@ -90,6 +90,9 @@ spec: - name: admin containerPort: 9180 protocol: TCP + - name: control + containerPort: 9090 + protocol: TCP volumeMounts: - name: config-writable mountPath: /usr/local/apisix/conf @@ -123,3 +126,18 @@ 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/framework/manifests/ingress.yaml b/test/e2e/framework/manifests/ingress.yaml index c4bb1014..119214a8 100644 --- a/test/e2e/framework/manifests/ingress.yaml +++ b/test/e2e/framework/manifests/ingress.yaml @@ -322,7 +322,7 @@ metadata: name: ingress-config data: config.yaml: | - log_level: "debug" + log_level: "info" controller_name: {{ .ControllerName | default "apisix.apache.org/apisix-ingress-controller" }} @@ -347,10 +347,10 @@ metadata: namespace: {{ .Namespace }} spec: ports: - - name: https - port: 8443 + - name: metrics + port: 8080 protocol: TCP - targetPort: 8443 + targetPort: 8080 selector: control-plane: controller-manager --- @@ -400,19 +400,16 @@ spec: initialDelaySeconds: 15 periodSeconds: 20 name: manager + ports: + - name: metrics + containerPort: 8080 + protocol: TCP readinessProbe: httpGet: path: /readyz port: 8081 initialDelaySeconds: 5 periodSeconds: 10 - resources: - limits: - cpu: 500m - memory: 128Mi - requests: - cpu: 10m - memory: 64Mi securityContext: allowPrivilegeEscalation: false capabilities: diff --git a/test/e2e/load-test/e2e_test.go b/test/e2e/load-test/e2e_test.go new file mode 100644 index 00000000..dcaef50c --- /dev/null +++ b/test/e2e/load-test/e2e_test.go @@ -0,0 +1,43 @@ +// 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 load_test + +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 TestLongTermStability(t *testing.T) { + RegisterFailHandler(Fail) + var f = framework.NewFramework() + _ = f + + scaffold.NewDeployer = func(s *scaffold.Scaffold) scaffold.Deployer { + return scaffold.NewAPISIXDeployer(s) + } + + _, _ = fmt.Fprintf(GinkgoWriter, "Starting load-test suite\n") + RunSpecs(t, "long-term-stability suite") +} diff --git a/test/e2e/load-test/spec_subject.go b/test/e2e/load-test/spec_subject.go new file mode 100644 index 00000000..7595c58a --- /dev/null +++ b/test/e2e/load-test/spec_subject.go @@ -0,0 +1,226 @@ +// 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 load + +import ( + "bytes" + "context" + "fmt" + "net/http" + "time" + + "github.com/api7/gopkg/pkg/log" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/wait" + + "github.com/apache/apisix-ingress-controller/test/e2e/framework" + "github.com/apache/apisix-ingress-controller/test/e2e/scaffold" +) + +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" +` + +const ingressClassYaml = ` +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: apisix +spec: + controller: "apisix.apache.org/apisix-ingress-controller" + parameters: + apiGroup: "apisix.apache.org" + kind: "GatewayProxy" + name: "apisix-proxy-config" + namespace: %s + scope: "Namespace" +` + +var _ = Describe("Load Test", func() { + var ( + s = scaffold.NewScaffold(&scaffold.Options{ + ControllerName: "apisix.apache.org/apisix-ingress-controller", + }) + controlAPIClient scaffold.ControlAPIClient + err error + ) + + 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.Namespace()), "") + Expect(err).NotTo(HaveOccurred(), "creating IngressClass") + time.Sleep(5 * time.Second) + + By("port-forward to control api service") + controlAPIClient, err = s.ControlAPIClient() + Expect(err).NotTo(HaveOccurred(), "create control api client") + }) + + Context("Load Test 2000 ApisixRoute", func() { + It("test 2000 ApisixRoute", func() { + const total = 2000 + + 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 +` + + By(fmt.Sprintf("prepare %d ApisixRoutes", total)) + var text = bytes.NewBuffer(nil) + for i := range total { + name := getRouteName(i) + _, err := fmt.Fprintf(text, apisixRouteSpec, name, name) + Expect(err).NotTo(HaveOccurred()) + text.WriteString("\n---\n") + } + err := s.CreateResourceFromString(text.String()) + Expect(err).NotTo(HaveOccurred(), "creating ApisixRoutes") + log.Infof("created %d ApisixRoutes", total) + + var ( + results []TestResult + now = time.Now() + ) + log.Infof("start calc time, now: %v", now) + By("Test the time required for applying a large number of ApisixRoutes to take effect") + var times int + err = 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 len(results) != total { + log.Debugw("number of effective services", zap.Int("number", len(results)), zap.Int("times", times)) + return false, nil + } + return len(results) == total, nil + }) + Expect(err).ShouldNot(HaveOccurred()) + costTime := time.Since(now) + log.Infof("end calc time, now: %v, costTime: %v", time.Now(), costTime) + results = append(results, TestResult{ + CaseName: fmt.Sprintf("Apply %d ApisixRoutes", total), + CostTime: costTime, + }) + + By("Test the time required for an ApisixRoute update to take effect") + var apisixRouteSpec0 = ` +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 +` + name := getRouteName(10) + err = s.CreateResourceFromString(fmt.Sprintf(apisixRouteSpec0, 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(time.Minute).ProbeEvery(100 * time.Millisecond).Should(Equal(http.StatusOK)) + results = append(results, TestResult{ + CaseName: fmt.Sprintf("Update a single ApisixRoute base on %d ApisixRoutes", total), + CostTime: time.Since(now), + }) + + PrintResults(results) + }) + }) +}) + +func getRouteName(i int) string { + return fmt.Sprintf("test-route-%04d", i) +} + +type TestResult struct { + CaseName string + CostTime time.Duration +} + +func (tr TestResult) String() string { + return fmt.Sprintf("%s takes effect for %s", tr.CaseName, tr.CostTime) +} + +func PrintResults(results []TestResult) { + fmt.Printf("\n======================TEST RESULT ProviderSyncPeriod %s===============================\n", framework.ProviderSyncPeriod) + fmt.Printf("%-70s", "Test Case") + fmt.Printf("%-70s\n", "Time Required") + fmt.Printf("%-70s\n", "--------------------------------------------------------------------------------------") + for _, result := range results { + fmt.Printf("%-70s", result.CaseName) + fmt.Printf("%-70s\n", result.CostTime) + } + fmt.Println("======================================================================================") + fmt.Println() +} diff --git a/test/e2e/scaffold/apisix_deployer.go b/test/e2e/scaffold/apisix_deployer.go index b6fe7feb..e0ef2d66 100644 --- a/test/e2e/scaffold/apisix_deployer.go +++ b/test/e2e/scaffold/apisix_deployer.go @@ -24,8 +24,7 @@ import ( "time" "github.com/gruntwork-io/terratest/modules/k8s" - . "github.com/onsi/ginkgo/v2" //nolint:staticcheck - . "github.com/onsi/gomega" //nolint:staticcheck + . "github.com/onsi/gomega" //nolint:staticcheck corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" @@ -111,36 +110,36 @@ func (s *APISIXDeployer) BeforeEach() { } func (s *APISIXDeployer) AfterEach() { - if CurrentSpecReport().Failed() { - if os.Getenv("TEST_ENV") == "CI" { - _, _ = fmt.Fprintln(GinkgoWriter, "Dumping namespace contents") - _, _ = k8s.RunKubectlAndGetOutputE(GinkgoT(), s.kubectlOptions, "get", "deploy,sts,svc,pods,gatewayproxy") - _, _ = k8s.RunKubectlAndGetOutputE(GinkgoT(), s.kubectlOptions, "describe", "pods") - } - - output := s.GetDeploymentLogs("apisix-ingress-controller") - if output != "" { - _, _ = fmt.Fprintln(GinkgoWriter, output) - } - } - - // Delete all additional gateways - for identifier := range s.additionalGateways { - err := s.CleanupAdditionalGateway(identifier) - Expect(err).NotTo(HaveOccurred(), "cleaning up additional gateway") - } - - // if the test case is successful, just delete namespace - err := k8s.DeleteNamespaceE(s.t, s.kubectlOptions, s.namespace) - Expect(err).NotTo(HaveOccurred(), "deleting namespace "+s.namespace) - - for i := len(s.finalizers) - 1; i >= 0; i-- { - runWithRecover(s.finalizers[i]) - } - - // Wait for a while to prevent the worker node being overwhelming - // (new cases will be run). - time.Sleep(3 * time.Second) + return + // if CurrentSpecReport().Failed() { + // if os.Getenv("TEST_ENV") == "CI" { + // _, _ = fmt.Fprintln(GinkgoWriter, "Dumping namespace contents") + // _, _ = k8s.RunKubectlAndGetOutputE(GinkgoT(), s.kubectlOptions, "get", "deploy,sts,svc,pods,gatewayproxy") + // _, _ = k8s.RunKubectlAndGetOutputE(GinkgoT(), s.kubectlOptions, "describe", "pods") + // } + + // output := s.GetDeploymentLogs("apisix-ingress-controller") + // if output != "" { + // _, _ = fmt.Fprintln(GinkgoWriter, output) + // } + // } + // // Delete all additional gateways + // for identifier := range s.additionalGateways { + // err := s.CleanupAdditionalGateway(identifier) + // Expect(err).NotTo(HaveOccurred(), "cleaning up additional gateway") + // } + + // // if the test case is successful, just delete namespace + // err := k8s.DeleteNamespaceE(s.t, s.kubectlOptions, s.namespace) + // Expect(err).NotTo(HaveOccurred(), "deleting namespace "+s.namespace) + + // for i := len(s.finalizers) - 1; i >= 0; i-- { + // runWithRecover(s.finalizers[i]) + // } + + // // Wait for a while to prevent the worker node being overwhelming + // // (new cases will be run). + // time.Sleep(3 * time.Second) } func (s *APISIXDeployer) DeployDataplane(deployOpts DeployDataplaneOptions) { @@ -260,7 +259,7 @@ func (s *APISIXDeployer) DeployIngress() { s.Framework.DeployIngress(framework.IngressDeployOpts{ ControllerName: s.opts.ControllerName, ProviderType: framework.ProviderType, - ProviderSyncPeriod: 200 * time.Millisecond, + ProviderSyncPeriod: getProviderSyncPeriod(), Namespace: s.namespace, Replicas: 1, }) @@ -270,12 +269,20 @@ func (s *APISIXDeployer) ScaleIngress(replicas int) { s.Framework.DeployIngress(framework.IngressDeployOpts{ ControllerName: s.opts.ControllerName, ProviderType: framework.ProviderType, - ProviderSyncPeriod: 200 * time.Millisecond, + ProviderSyncPeriod: getProviderSyncPeriod(), Namespace: s.namespace, Replicas: replicas, }) } +func getProviderSyncPeriod() time.Duration { + providerSyncPeriod, err := time.ParseDuration(framework.ProviderSyncPeriod) + if err != nil { + providerSyncPeriod = 5 * time.Second + } + return providerSyncPeriod +} + // getEnvOrDefault returns environment variable value or default func getEnvOrDefault(key, defaultValue string) string { if value := os.Getenv(key); value != "" { diff --git a/test/e2e/scaffold/scaffold.go b/test/e2e/scaffold/scaffold.go index fe533a82..36e02588 100644 --- a/test/e2e/scaffold/scaffold.go +++ b/test/e2e/scaffold/scaffold.go @@ -20,6 +20,7 @@ package scaffold import ( "context" "crypto/tls" + "encoding/json" "fmt" "net/http" "net/url" @@ -31,6 +32,7 @@ import ( "github.com/gruntwork-io/terratest/modules/testing" . "github.com/onsi/ginkgo/v2" //nolint:staticcheck . "github.com/onsi/gomega" //nolint:staticcheck + "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -443,3 +445,34 @@ func NewClient(scheme, host string) *httpexpect.Expect { ), }) } + +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 +} + +type ControlAPIClient interface { + ListServices() ([]any, int64, error) +} + +type controlAPI struct { + client *httpexpect.Expect +} + +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, errors.Wrap(err, "failed to unmarshal response body") + } + return result, int64(len(result)), err +}