diff --git a/.dockerignore b/.dockerignore index ab088f702..b5695a33b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,16 +1,14 @@ -/target +*.md +*.yaml +*.yml +.* +/LICENSE /bin -.git -.gitignore /charts -/.vscode -/bin /cli -/examples +/config /docs -/.envrc -/.github -/README.md -/RELEASE_PROCESS.md -CONTRIBUTING.md +/examples +/target +/tests Dockerfile diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 7fb95228a..acd3e7158 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -53,6 +53,9 @@ jobs: - name: Manifests run: make verify-manifests + - name: Mockgen + run: make verify-mockgen + - name: Build run: ARCH=${{ matrix.name }} make build @@ -71,5 +74,5 @@ jobs: with: go-version: 1.19 - name: Get golangci - run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.49.0 + run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.52.2 - uses: pre-commit/action@v3.0.0 diff --git a/.golangci.yml b/.golangci.yml index 758d0c351..82b09266e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -49,6 +49,14 @@ issues: - gomnd - dupl - unparam + # Exclude gci check for //+kubebuilder:scaffold:imports comments. Waiting to + # resolve https://github.com/kedacore/keda/issues/4379 + - path: operator/controllers/http/suite_test.go + linters: + - gci + - path: operator/main.go + linters: + - gci linters-settings: funlen: lines: 80 diff --git a/CHANGELOG.md b/CHANGELOG.md index cf1b4d96c..40291df9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ This changelog keeps track of work items that have been completed and are ready - **General**: Automatically tag Docker image with commit SHA ([#567](https://github.com/kedacore/http-add-on/issues/567)) - **RBAC**: Introduce fine-grained permissions per component and reduce required permissions ([#612](https://github.com/kedacore/http-add-on/issues/612)) - **Operator**: Migrate project to Kubebuilder v3 ([#625](https://github.com/kedacore/http-add-on/issues/625)) +- **Routing**: New HTTPSO-based Routing Table ([#605](https://github.com/kedacore/http-add-on/issues/605)) ### Fixes diff --git a/Makefile b/Makefile index d0334cdf1..fabc0d391 100644 --- a/Makefile +++ b/Makefile @@ -85,9 +85,9 @@ publish-multiarch: publish-operator-multiarch publish-interceptor-multiarch publ # Development -generate: codegen manifests ## Generate code and manifests. +generate: codegen manifests mockgen ## Generate code, manifests, and mocks. -verify: verify-codegen verify-manifests ## Verify code and manifests. +verify: verify-codegen verify-manifests verify-mockgen ## Verify code, manifests, and mocks. codegen: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. $(CONTROLLER_GEN) object:headerFile='hack/boilerplate.go.txt' paths='./...' @@ -104,6 +104,12 @@ manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and Cust verify-manifests: ## Verify manifests are up to date. ./hack/verify-codegen.sh +mockgen: ## Generate mock implementations of Go interfaces. + ./hack/update-mockgen.sh + +verify-mockgen: ## Verify mocks are up to date. + ./hack/verify-mockgen.sh + fmt: ## Run go fmt against code. go fmt ./... diff --git a/config/interceptor/deployment.yaml b/config/interceptor/deployment.yaml index bf0fa2bd7..447233a91 100644 --- a/config/interceptor/deployment.yaml +++ b/config/interceptor/deployment.yaml @@ -53,11 +53,18 @@ spec: - name: KEDA_HTTP_EXPECT_CONTINUE_TIMEOUT value: "1s" ports: - # TODO(pedrotorres): remove after implementing new routing table - name: admin containerPort: 9090 - name: proxy containerPort: 8080 + livenessProbe: + httpGet: + path: /livez + port: proxy + readinessProbe: + httpGet: + path: /readyz + port: proxy # TODO(pedrotorres): set better default values avoiding overcommitment resources: requests: @@ -69,11 +76,12 @@ spec: securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true - runAsNonRoot: true capabilities: drop: - - ALL - seccompProfile: - type: RuntimeDefault + - ALL + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault serviceAccountName: interceptor terminationGracePeriodSeconds: 10 diff --git a/config/interceptor/role.yaml b/config/interceptor/role.yaml index 3d5e0d990..3397a4858 100644 --- a/config/interceptor/role.yaml +++ b/config/interceptor/role.yaml @@ -13,18 +13,10 @@ rules: - get - list - watch ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - creationTimestamp: null - name: interceptor - namespace: keda -rules: - apiGroups: - - "" + - http.keda.sh resources: - - configmaps + - httpscaledobjects verbs: - get - list diff --git a/config/interceptor/role_binding.yaml b/config/interceptor/role_binding.yaml index 25459fcf0..fbed8bb04 100644 --- a/config/interceptor/role_binding.yaml +++ b/config/interceptor/role_binding.yaml @@ -10,15 +10,3 @@ roleRef: subjects: - kind: ServiceAccount name: interceptor ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: interceptor -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: interceptor -subjects: -- kind: ServiceAccount - name: interceptor diff --git a/config/operator/deployment.yaml b/config/operator/deployment.yaml index e84fcaf08..8ddfc1fbc 100644 --- a/config/operator/deployment.yaml +++ b/config/operator/deployment.yaml @@ -57,11 +57,12 @@ spec: securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true - runAsNonRoot: true capabilities: drop: - - ALL - seccompProfile: - type: RuntimeDefault + - ALL + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault serviceAccountName: operator terminationGracePeriodSeconds: 10 diff --git a/config/scaler/deployment.yaml b/config/scaler/deployment.yaml index abacafc10..31a1020de 100644 --- a/config/scaler/deployment.yaml +++ b/config/scaler/deployment.yaml @@ -43,8 +43,6 @@ spec: ports: - name: grpc containerPort: 9090 - - name: health - containerPort: 9091 # TODO(pedrotorres): set better default values avoiding overcommitment resources: requests: @@ -56,11 +54,12 @@ spec: securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true - runAsNonRoot: true capabilities: drop: - ALL - seccompProfile: - type: RuntimeDefault + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault serviceAccountName: scaler terminationGracePeriodSeconds: 10 diff --git a/config/scaler/role.yaml b/config/scaler/role.yaml index 5d136daea..069375825 100644 --- a/config/scaler/role.yaml +++ b/config/scaler/role.yaml @@ -21,6 +21,14 @@ rules: - get - list - watch +- apiGroups: + - http.keda.sh + resources: + - httpscaledobjects + verbs: + - get + - list + - watch --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role diff --git a/e2e/k8s.go b/e2e/k8s.go index 1f3a7b6d5..5c90e01ad 100644 --- a/e2e/k8s.go +++ b/e2e/k8s.go @@ -9,8 +9,6 @@ import ( "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" clientconfig "sigs.k8s.io/controller-runtime/pkg/client/config" - - "github.com/kedacore/http-add-on/pkg/k8s" ) func getClient() ( @@ -35,5 +33,5 @@ func deleteNS(ns string) error { func getScaledObject(ctx context.Context, cl client.Client, ns string, name string) error { var scaledObject kedav1alpha1.ScaledObject - return cl.Get(ctx, k8s.ObjKey(ns, name), &scaledObject) + return cl.Get(ctx, client.ObjectKey{Namespace: ns, Name: name}, &scaledObject) } diff --git a/e2e/wait_deployment.go b/e2e/wait_deployment.go index f1f1dae9c..3f608d9e1 100644 --- a/e2e/wait_deployment.go +++ b/e2e/wait_deployment.go @@ -8,8 +8,6 @@ import ( "golang.org/x/sync/errgroup" appsv1 "k8s.io/api/apps/v1" "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/kedacore/http-add-on/pkg/k8s" ) func waitUntilDeployment( @@ -23,7 +21,7 @@ func waitUntilDeployment( depl := &appsv1.Deployment{} if err := cl.Get( ctx, - k8s.ObjKey(ns, name), + client.ObjectKey{Namespace: ns, Name: name}, depl, ); err != nil { return err diff --git a/go.mod b/go.mod index 27bea5a2c..e4de3b8cb 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ require ( github.com/codeskyblue/go-sh v0.0.0-20200712050446-30169cf553fe github.com/go-logr/logr v1.2.4 github.com/go-logr/zapr v1.2.3 + github.com/golang/mock v1.7.0-rc.1.0.20220812172401-5b455625bd2c + github.com/hashicorp/go-immutable-radix/v2 v2.0.0 github.com/kedacore/keda/v2 v2.10.0 github.com/kelseyhightower/envconfig v1.4.0 github.com/onsi/ginkgo/v2 v2.9.4 @@ -45,6 +47,7 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20230309165930-d61513b1440d // indirect github.com/google/uuid v1.3.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.0 // indirect github.com/imdario/mergo v0.3.14 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/go.sum b/go.sum index d5a8acbc9..8dadcdb76 100644 --- a/go.sum +++ b/go.sum @@ -63,6 +63,8 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.7.0-rc.1.0.20220812172401-5b455625bd2c h1:8AzxBXzXPCzl8EEsgWirPPDA5ru+bm5dVEV/KkpAKnE= +github.com/golang/mock v1.7.0-rc.1.0.20220812172401-5b455625bd2c/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -100,6 +102,11 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/go-immutable-radix/v2 v2.0.0 h1:nq9lQ5I71Heg2lRb2/+szuIWKY3Y73d8YKyXyN91WzU= +github.com/hashicorp/go-immutable-radix/v2 v2.0.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/golang-lru/v2 v2.0.0 h1:Lf+9eD8m5pncvHAOCQj49GSN6aQI8XGfI5OpXNkoWaA= +github.com/hashicorp/golang-lru/v2 v2.0.0/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/imdario/mergo v0.3.14 h1:fOqeC1+nCuuk6PKQdg9YmosXX7Y7mHX6R/0ZldI9iHo= github.com/imdario/mergo v0.3.14/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -173,6 +180,7 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1: github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= 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.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= @@ -190,12 +198,14 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20221215174704-0915cd710c24 h1:6w3iSY8IIkp5OQtbYj8NeuKG1jS9d+kYaubXqsoOiQ8= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -211,6 +221,7 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -223,6 +234,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ 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= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -234,6 +246,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -245,6 +258,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= @@ -259,6 +273,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/hack/tools.go b/hack/tools.go index 596af0aa4..3c8ea465f 100644 --- a/hack/tools.go +++ b/hack/tools.go @@ -22,5 +22,6 @@ limitations under the License. package hack import ( + _ "github.com/golang/mock/mockgen" _ "k8s.io/code-generator" ) diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index 21e5d8e6f..db68221ae 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -24,8 +24,8 @@ SCRIPT_ROOT="$(dirname "${BASH_SOURCE[0]}")/.." OUTPUT_BASE="$(mktemp -d)" GO_PACKAGE='github.com/kedacore/http-add-on' -GEN_SUFFIX="operator/generated" -API_SUFFIX="operator/apis" +GEN_SUFFIX='operator/generated' +API_SUFFIX='operator/apis' . "${CODEGEN_PKG}/generate-groups.sh" \ 'client,informer,lister' \ diff --git a/hack/update-mockgen.sh b/hack/update-mockgen.sh new file mode 100755 index 000000000..52a2a8df2 --- /dev/null +++ b/hack/update-mockgen.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +# Copyright 2017 The Kubernetes Authors. +# Copyright 2023 The KEDA Authors. +# +# Licensed 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. + +set -o errexit +set -o nounset +set -o pipefail + +OUTPUT="$(mktemp -d)" + +GEN='operator/generated' +CPY='hack/boilerplate.go.txt' +PKG='mock' + +MOCKGEN_PKG="${MOCKGEN_PKG:-$(go list -f '{{ .Dir }}' -m github.com/golang/mock 2>/dev/null)/mockgen}" +MOCKGEN="${OUTPUT}/mockgen" +go build -o "${MOCKGEN}" "${MOCKGEN_PKG}" + +for SRC in $(find "${GEN}" -type 'f' -name '*.go' | grep -v '/fake/' | grep -v "/${PKG}/") +do + DIR="$(dirname "${SRC}")/${PKG}" + mkdir -p "${DIR}" + DST="${DIR}/$(basename "${SRC}")" + "${MOCKGEN}" -copyright_file="${CPY}" -destination="${DST}" -package="${PKG}" -source="${SRC}" +done + +rm -fR "${OUTPUT}" diff --git a/hack/verify-mockgen.sh b/hack/verify-mockgen.sh new file mode 100755 index 000000000..02aa5f047 --- /dev/null +++ b/hack/verify-mockgen.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +# Copyright 2017 The Kubernetes Authors. +# Copyright 2023 The KEDA Authors. +# +# Licensed 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. + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. + +DIFFROOT="${SCRIPT_ROOT}/pkg" +TMP_DIFFROOT="${SCRIPT_ROOT}/_tmp/pkg" +_tmp="${SCRIPT_ROOT}/_tmp" + +cleanup() { + rm -rf "${_tmp}" +} +trap "cleanup" EXIT SIGINT + +cleanup + +mkdir -p "${TMP_DIFFROOT}" +cp -a "${DIFFROOT}"/* "${TMP_DIFFROOT}" + +make mockgen +echo "diffing ${DIFFROOT} against freshly generated mockgen" +ret=0 +diff -Naupr "${DIFFROOT}" "${TMP_DIFFROOT}" || ret=$? +cp -a "${TMP_DIFFROOT}"/* "${DIFFROOT}" +if [[ $ret -eq 0 ]] +then + echo "${DIFFROOT} up to date." +else + echo "${DIFFROOT} is out of date. Please run '${SCRIPT_ROOT}/hack/update-mockgen.sh'" + exit 1 +fi diff --git a/interceptor/Dockerfile b/interceptor/Dockerfile index 0a99a3f10..89bcd5a8d 100644 --- a/interceptor/Dockerfile +++ b/interceptor/Dockerfile @@ -1,29 +1,14 @@ -# Build the adapter binary -FROM --platform=$BUILDPLATFORM ghcr.io/kedacore/build-tools:1.19.5 as builder - -ARG VERSION=main -ARG GIT_COMMIT=HEAD - +FROM --platform=${BUILDPLATFORM} ghcr.io/kedacore/build-tools:1.20.4 as builder WORKDIR /workspace - -COPY go.mod go.mod -COPY go.sum go.sum +COPY go.* . RUN go mod download - COPY . . - -# Build -# https://www.docker.com/blog/faster-multi-platform-builds-dockerfile-cross-compilation-guide/ +ARG VERSION=main +ARG GIT_COMMIT=HEAD ARG TARGETOS ARG TARGETARCH -RUN VERSION=${VERSION} GIT_COMMIT=${GIT_COMMIT} TARGET_OS=$TARGETOS ARCH=$TARGETARCH make build-interceptor - +RUN VERSION="${VERSION}" GIT_COMMIT="${GIT_COMMIT}" TARGET_OS="${TARGETOS}" ARCH="${TARGETARCH}" make build-interceptor FROM gcr.io/distroless/static:nonroot -WORKDIR / -COPY --from=builder /workspace/bin/interceptor . -# 65532 is numeric for nonroot -USER 65532:65532 -EXPOSE 8080 - -ENTRYPOINT ["/interceptor"] +COPY --from=builder /workspace/bin/interceptor /sbin/init +ENTRYPOINT ["/sbin/init"] diff --git a/interceptor/config/serving.go b/interceptor/config/serving.go index 10068ef44..a3ff5879e 100644 --- a/interceptor/config/serving.go +++ b/interceptor/config/serving.go @@ -12,6 +12,9 @@ type Serving struct { // CurrentNamespace is the namespace that the interceptor is // currently running in CurrentNamespace string `envconfig:"KEDA_HTTP_CURRENT_NAMESPACE" required:"true"` + // WatchNamespace is the namespace to watch for new HTTPScaledObjects. + // Leave this empty to watch HTTPScaledObjects in all namespaces. + WatchNamespace string `envconfig:"KEDA_HTTP_WATCH_NAMESPACE" default:""` // ProxyPort is the port that the public proxy should run on ProxyPort int `envconfig:"KEDA_HTTP_PROXY_PORT" required:"true"` // AdminPort is the port that the internal admin server should run on. diff --git a/interceptor/main.go b/interceptor/main.go index 2cfa56821..00efdab5f 100644 --- a/interceptor/main.go +++ b/interceptor/main.go @@ -2,19 +2,20 @@ package main import ( "context" - "encoding/json" "fmt" "math/rand" - nethttp "net/http" + "net/http" "os" "time" "github.com/go-logr/logr" "golang.org/x/sync/errgroup" "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" "github.com/kedacore/http-add-on/interceptor/config" + clientset "github.com/kedacore/http-add-on/operator/generated/clientset/versioned" + informers "github.com/kedacore/http-add-on/operator/generated/informers/externalversions" "github.com/kedacore/http-add-on/pkg/build" kedahttp "github.com/kedacore/http-add-on/pkg/http" "github.com/kedacore/http-add-on/pkg/k8s" @@ -28,8 +29,8 @@ func init() { rand.Seed(time.Now().UnixNano()) } -// +kubebuilder:rbac:groups="",namespace=keda,resources=configmaps,verbs=get;list;watch // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch +// +kubebuilder:rbac:groups=http.keda.sh,resources=httpscaledobjects,verbs=get;list;watch func main() { lggr, err := pkglog.NewZapr() @@ -57,7 +58,7 @@ func main() { proxyPort := servingCfg.ProxyPort adminPort := servingCfg.AdminPort - cfg, err := rest.InClusterConfig() + cfg, err := ctrl.GetConfig() if err != nil { lggr.Error(err, "Kubernetes client config not found") os.Exit(1) @@ -77,35 +78,20 @@ func main() { os.Exit(1) } - configMapsInterface := cl.CoreV1().ConfigMaps(servingCfg.CurrentNamespace) - waitFunc := newDeployReplicasForwardWaitFunc(lggr, deployCache) lggr.Info("Interceptor starting") q := queue.NewMemory() - routingTable := routing.NewTable() - // Create the informer of ConfigMap resource, - // the resynchronization period of the informer should be not less than 1s, - // refer to: https://github.com/kubernetes/client-go/blob/v0.22.2/tools/cache/shared_informer.go#L475 - configMapInformer := k8s.NewInformerConfigMapUpdater( - lggr, - cl, - servingCfg.ConfigMapCacheRsyncPeriod, - servingCfg.CurrentNamespace, - ) - - lggr.Info( - "Fetching initial routing table", - ) - if err := routing.GetTable( - ctx, - lggr, - configMapsInterface, - routingTable, - q, - ); err != nil { + httpCl, err := clientset.NewForConfig(cfg) + if err != nil { + lggr.Error(err, "creating new HTTP ClientSet") + os.Exit(1) + } + sharedInformerFactory := informers.NewSharedInformerFactory(httpCl, servingCfg.ConfigMapCacheRsyncPeriod) + routingTable, err := routing.NewTable(sharedInformerFactory, servingCfg.WatchNamespace) + if err != nil { lggr.Error(err, "fetching routing table") os.Exit(1) } @@ -125,14 +111,7 @@ func main() { // enter and exit the system errGrp.Go(func() error { defer ctxDone() - err := routing.StartConfigMapRoutingTableUpdater( - ctx, - lggr, - configMapInformer, - servingCfg.CurrentNamespace, - routingTable, - nil, - ) + err := routingTable.Start(ctx) lggr.Error(err, "config map routing table updater failed") return err }) @@ -149,13 +128,8 @@ func main() { err := runAdminServer( ctx, lggr, - configMapsInterface, q, - routingTable, - deployCache, adminPort, - servingCfg, - timeoutCfg, ) lggr.Error(err, "admin server failed") return err @@ -194,43 +168,16 @@ func main() { func runAdminServer( ctx context.Context, lggr logr.Logger, - cmGetter k8s.ConfigMapGetter, q queue.Counter, - routingTable *routing.Table, - deployCache k8s.DeploymentCache, port int, - servingConfig *config.Serving, - timeoutConfig *config.Timeouts, ) error { lggr = lggr.WithName("runAdminServer") - adminServer := nethttp.NewServeMux() + adminServer := http.NewServeMux() queue.AddCountsRoute( lggr, adminServer, q, ) - routing.AddFetchRoute( - lggr, - adminServer, - routingTable, - ) - routing.AddPingRoute( - lggr, - adminServer, - cmGetter, - routingTable, - q, - ) - adminServer.HandleFunc( - "/deployments", - func(w nethttp.ResponseWriter, r *nethttp.Request) { - if err := json.NewEncoder(w).Encode(deployCache); err != nil { - lggr.Error(err, "encoding deployment cache") - } - }, - ) - kedahttp.AddConfigEndpoint(lggr, adminServer, servingConfig, timeoutConfig) - kedahttp.AddVersionEndpoint(lggr.WithName("interceptorAdmin"), adminServer) addr := fmt.Sprintf("0.0.0.0:%d", port) lggr.Info("admin server starting", "address", addr) @@ -242,7 +189,7 @@ func runProxyServer( lggr logr.Logger, q queue.Counter, waitFunc forwardWaitFunc, - routingTable *routing.Table, + routingTable routing.Table, timeouts *config.Timeouts, port int, ) error { @@ -257,7 +204,6 @@ func runProxyServer( routingTable, dialContextFunc, waitFunc, - routing.ServiceURL, newForwardingConfigFromTimeouts(timeouts), ), ) diff --git a/interceptor/main_test.go b/interceptor/main_test.go index f86531c15..5a30a945c 100644 --- a/interceptor/main_test.go +++ b/interceptor/main_test.go @@ -21,7 +21,6 @@ import ( "github.com/kedacore/http-add-on/pkg/k8s" kedanet "github.com/kedacore/http-add-on/pkg/net" "github.com/kedacore/http-add-on/pkg/queue" - "github.com/kedacore/http-add-on/pkg/routing" "github.com/kedacore/http-add-on/pkg/test" ) @@ -49,20 +48,19 @@ func TestRunProxyServerCountMiddleware(t *testing.T) { r.NoError(err) g, ctx := errgroup.WithContext(ctx) q := queue.NewFakeCounter() - routingTable := routing.NewTable() + // set up a fake host that we can spoof // when we later send request to the proxy, // so that the proxy calculates a URL for that // host that points to the (above) fake origin - // server. - r.NoError(routingTable.AddTarget( - host, - targetFromURL( - originURL, - originPort, - "testdepl", - ), - )) + // server + routingTable := newTestRoutingTable() + routingTable.memory[host] = targetFromURL( + originURL, + originPort, + "testdepl", + ) + timeouts := &config.Timeouts{} waiterCh := make(chan struct{}) waitFunc := func(_ context.Context, _, _ string) (int, error) { @@ -170,9 +168,7 @@ func TestRunAdminServerDeploymentsEndpoint(t *testing.T) { return runAdminServer( ctx, lggr, - k8s.FakeConfigMapGetter{}, queue.NewFakeCounter(), - routing.NewTable(), deplCache, port, srvCfg, @@ -229,9 +225,7 @@ func TestRunAdminServerConfig(t *testing.T) { return runAdminServer( ctx, lggr, - k8s.FakeConfigMapGetter{}, queue.NewFakeCounter(), - routing.NewTable(), k8s.NewFakeDeploymentCache(), port, srvCfg, diff --git a/interceptor/proxy_handlers.go b/interceptor/proxy_handlers.go index 7f7215c61..aa9eedc34 100644 --- a/interceptor/proxy_handlers.go +++ b/interceptor/proxy_handlers.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "net/http" + "net/url" + "strings" "time" "github.com/go-logr/logr" @@ -43,10 +45,9 @@ func newForwardingConfigFromTimeouts(t *config.Timeouts) forwardingConfig { // create a URL with url.Parse("https://...") func newForwardingHandler( lggr logr.Logger, - routingTable *routing.Table, + routingTable routing.Table, dialCtxFunc kedanet.DialContextFunc, waitFunc forwardWaitFunc, - targetSvcURL routing.ServiceURLFunc, fwdCfg forwardingConfig, ) http.Handler { roundTripper := &http.Transport{ @@ -60,20 +61,18 @@ func newForwardingHandler( ResponseHeaderTimeout: fwdCfg.respHeaderTimeout, } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - host, err := getHost(r) - if err != nil { - w.WriteHeader(400) - if _, err := w.Write([]byte("Host not found in request")); err != nil { - lggr.Error(err, "could not write error response to client") + routingTarget := routingTable.Route(r) + if routingTarget == nil { + // return 200 for kube-probe + if ua := r.UserAgent(); strings.HasPrefix(ua, "kube-probe/") { + return } - return - } - routingTarget, err := routingTable.Lookup(host) - if err != nil { - w.WriteHeader(404) + + w.WriteHeader(http.StatusNotFound) if _, err := w.Write([]byte(fmt.Sprintf("Host %s not found", r.Host))); err != nil { lggr.Error(err, "could not send error message to client") } + return } @@ -82,20 +81,26 @@ func newForwardingHandler( replicas, err := waitFunc( waitFuncCtx, routingTarget.Namespace, - routingTarget.Deployment, + routingTarget.Spec.ScaleTargetRef.Deployment, ) if err != nil { lggr.Error(err, "wait function failed, not forwarding request") - w.WriteHeader(502) + w.WriteHeader(http.StatusBadGateway) if _, err := w.Write([]byte(fmt.Sprintf("error on backend (%s)", err))); err != nil { lggr.Error(err, "could not write error response to client") } return } - targetSvcURL, err := targetSvcURL(*routingTarget) + //goland:noinspection HttpUrlsUsage + targetSvcURL, err := url.Parse(fmt.Sprintf( + "http://%s.%s:%d", + routingTarget.Spec.ScaleTargetRef.Service, + routingTarget.Namespace, + routingTarget.Spec.ScaleTargetRef.Port, + )) if err != nil { lggr.Error(err, "forwarding failed") - w.WriteHeader(500) + w.WriteHeader(http.StatusInternalServerError) if _, err := w.Write([]byte("error getting backend service URL")); err != nil { lggr.Error(err, "could not write error response to client") } diff --git a/interceptor/proxy_handlers_integration_test.go b/interceptor/proxy_handlers_integration_test.go index 4e1456480..ab55e54c1 100644 --- a/interceptor/proxy_handlers_integration_test.go +++ b/interceptor/proxy_handlers_integration_test.go @@ -21,6 +21,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" + httpv1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1" "github.com/kedacore/http-add-on/pkg/k8s" kedanet "github.com/kedacore/http-add-on/pkg/net" "github.com/kedacore/http-add-on/pkg/routing" @@ -32,7 +33,6 @@ func TestIntegrationHappyPath(t *testing.T) { deploymentReplicasTimeout = 200 * time.Millisecond responseHeaderTimeout = 1 * time.Second deplName = "testdeployment" - namespace = "testns" ) r := require.New(t) h, err := newHarness( @@ -43,7 +43,17 @@ func TestIntegrationHappyPath(t *testing.T) { defer h.close() t.Logf("Harness: %s", h.String()) - h.deplCache.Set(namespace, deplName, appsv1.Deployment{ + originPort, err := strconv.Atoi(h.originURL.Port()) + r.NoError(err) + + target := targetFromURL( + h.originURL, + originPort, + deplName, + ) + h.routingTable.(*testRoutingTable).memory[hostForTest(t)] = target + + h.deplCache.Set(target.GetNamespace(), deplName, appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: deplName}, Spec: appsv1.DeploymentSpec{ // note that the forwarding wait function doesn't care about @@ -57,14 +67,6 @@ func TestIntegrationHappyPath(t *testing.T) { }, }) - originPort, err := strconv.Atoi(h.originURL.Port()) - r.NoError(err) - r.NoError(h.routingTable.AddTarget(hostForTest(t), targetFromURL( - h.originURL, - originPort, - deplName, - ))) - // happy path res, err := doRequest( http.DefaultClient, @@ -83,20 +85,10 @@ func TestIntegrationHappyPath(t *testing.T) { // need to set the replicas to 1, but we're doing so anyway to // isolate the routing table behavior func TestIntegrationNoRoutingTableEntry(t *testing.T) { - const ( - ns = "testns" - ) - host := fmt.Sprintf("%s.integrationtest.interceptor.kedahttp.dev", t.Name()) r := require.New(t) h, err := newHarness(t, time.Second) r.NoError(err) defer h.close() - h.deplCache.Set(ns, host, appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{Name: host}, - Spec: appsv1.DeploymentSpec{ - Replicas: i32Ptr(1), - }, - }) // not in the routing table res, err := doRequest( @@ -114,7 +106,6 @@ func TestIntegrationNoRoutingTableEntry(t *testing.T) { func TestIntegrationNoReplicas(t *testing.T) { const ( deployTimeout = 100 * time.Millisecond - ns = "testns" ) host := hostForTest(t) deployName := "testdeployment" @@ -124,14 +115,16 @@ func TestIntegrationNoReplicas(t *testing.T) { originPort, err := strconv.Atoi(h.originURL.Port()) r.NoError(err) - r.NoError(h.routingTable.AddTarget(hostForTest(t), targetFromURL( + + target := targetFromURL( h.originURL, originPort, deployName, - ))) + ) + h.routingTable.(*testRoutingTable).memory[hostForTest(t)] = target // 0 replicas - h.deplCache.Set(ns, deployName, appsv1.Deployment{ + h.deplCache.Set(target.GetNamespace(), deployName, appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: deployName}, Spec: appsv1.DeploymentSpec{ Replicas: i32Ptr(0), @@ -159,7 +152,6 @@ func TestIntegrationWaitReplicas(t *testing.T) { const ( deployTimeout = 2 * time.Second responseTimeout = 1 * time.Second - ns = "testns" deployName = "testdeployment" ) ctx := context.Background() @@ -170,14 +162,13 @@ func TestIntegrationWaitReplicas(t *testing.T) { // add host to routing table originPort, err := strconv.Atoi(h.originURL.Port()) r.NoError(err) - r.NoError(h.routingTable.AddTarget( - hostForTest(t), - targetFromURL( - h.originURL, - originPort, - deployName, - ), - )) + + target := targetFromURL( + h.originURL, + originPort, + deployName, + ) + h.routingTable.(*testRoutingTable).memory[hostForTest(t)] = target // set up a deployment with zero replicas and create // a watcher we can use later to fake-send a deployment @@ -188,8 +179,8 @@ func TestIntegrationWaitReplicas(t *testing.T) { Replicas: i32Ptr(0), }, } - h.deplCache.Set(ns, deployName, initialDeployment) - watcher := h.deplCache.SetWatcher(ns, deployName) + h.deplCache.Set(target.GetNamespace(), deployName, initialDeployment) + watcher := h.deplCache.SetWatcher(target.GetNamespace(), deployName) // make the request in one goroutine, and in the other, wait a bit // and then add replicas to the deployment cache @@ -265,7 +256,7 @@ type harness struct { originHdl http.Handler originSrv *httptest.Server originURL *url.URL - routingTable *routing.Table + routingTable routing.Table dialCtxFunc kedanet.DialContextFunc deplCache *k8s.FakeDeploymentCache waitFunc forwardWaitFunc @@ -277,7 +268,7 @@ func newHarness( ) (*harness, error) { t.Helper() lggr := logr.Discard() - routingTable := routing.NewTable() + routingTable := newTestRoutingTable() dialContextFunc := kedanet.DialContextWithRetry( &net.Dialer{ Timeout: 2 * time.Second, @@ -311,9 +302,6 @@ func newHarness( routingTable, dialContextFunc, waitFunc, - func(routing.Target) (*url.URL, error) { - return originSrvURL, nil - }, forwardingConfig{ waitTimeout: deployReplicasTimeout, respHeaderTimeout: time.Second, @@ -378,3 +366,27 @@ func splitHostPort(hostPortStr string) (string, int, error) { } return host, port, nil } + +type testRoutingTable struct { + memory map[string]*httpv1alpha1.HTTPScaledObject +} + +func newTestRoutingTable() *testRoutingTable { + return &testRoutingTable{ + memory: make(map[string]*httpv1alpha1.HTTPScaledObject), + } +} + +var _ routing.Table = (*testRoutingTable)(nil) + +func (t testRoutingTable) Start(_ context.Context) error { + return nil +} + +func (t testRoutingTable) Route(req *http.Request) *httpv1alpha1.HTTPScaledObject { + return t.memory[req.Host] +} + +func (t testRoutingTable) HasSynced() bool { + return true +} diff --git a/interceptor/proxy_handlers_test.go b/interceptor/proxy_handlers_test.go index 25bf7d6ea..1f15c96c5 100644 --- a/interceptor/proxy_handlers_test.go +++ b/interceptor/proxy_handlers_test.go @@ -12,9 +12,11 @@ import ( "github.com/go-logr/logr" "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" + httpv1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1" kedanet "github.com/kedacore/http-add-on/pkg/net" - "github.com/kedacore/http-add-on/pkg/routing" ) // the proxy should successfully forward a request to a running server @@ -32,7 +34,7 @@ func TestImmediatelySuccessfulProxy(t *testing.T) { srv, originURL, err := kedanet.StartTestServer(originHdl) r.NoError(err) defer srv.Close() - routingTable := routing.NewTable() + routingTable := newTestRoutingTable() originPort, err := strconv.Atoi(originURL.Port()) r.NoError(err) target := targetFromURL( @@ -40,7 +42,7 @@ func TestImmediatelySuccessfulProxy(t *testing.T) { originPort, "testdepl", ) - r.NoError(routingTable.AddTarget(host, target)) + routingTable.memory[host] = target timeouts := defaultTimeouts() dialCtxFunc := retryDialContextFunc(timeouts, timeouts.DefaultBackoff()) @@ -52,9 +54,6 @@ func TestImmediatelySuccessfulProxy(t *testing.T) { routingTable, dialCtxFunc, waitFunc, - func(routing.Target) (*url.URL, error) { - return originURL, nil - }, forwardingConfig{ waitTimeout: timeouts.DeploymentReplicas, respHeaderTimeout: timeouts.ResponseHeader, @@ -88,23 +87,26 @@ func TestWaitFailedConnection(t *testing.T) { waitFunc := func(context.Context, string, string) (int, error) { return 1, nil } - routingTable := routing.NewTable() - r.NoError(routingTable.AddTarget(host, routing.NewTarget( - "testns", - "nosuchdepl", - 8081, - "nosuchdepl", - 1234, - ))) + routingTable := newTestRoutingTable() + routingTable.memory[host] = &httpv1alpha1.HTTPScaledObject{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "testns", + }, + Spec: httpv1alpha1.HTTPScaledObjectSpec{ + ScaleTargetRef: &httpv1alpha1.ScaleTargetRef{ + Deployment: "nosuchdepl", + Service: "nosuchdepl", + Port: 8081, + }, + TargetPendingRequests: pointer.Int32(1234), + }, + } hdl := newForwardingHandler( logr.Discard(), routingTable, dialCtxFunc, waitFunc, - func(routing.Target) (*url.URL, error) { - return url.Parse("http://nosuchhost:8081") - }, forwardingConfig{ waitTimeout: timeouts.DeploymentReplicas, respHeaderTimeout: timeouts.ResponseHeader, @@ -135,22 +137,25 @@ func TestTimesOutOnWaitFunc(t *testing.T) { defer finishWaitFunc() noSuchHost := fmt.Sprintf("%s.testing", t.Name()) - routingTable := routing.NewTable() - r.NoError(routingTable.AddTarget(noSuchHost, routing.NewTarget( - "testns", - "nosuchsvc", - 9091, - "nosuchdepl", - 1234, - ))) + routingTable := newTestRoutingTable() + routingTable.memory[noSuchHost] = &httpv1alpha1.HTTPScaledObject{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "testns", + }, + Spec: httpv1alpha1.HTTPScaledObjectSpec{ + ScaleTargetRef: &httpv1alpha1.ScaleTargetRef{ + Deployment: "nosuchdepl", + Service: "nosuchsvc", + Port: 9091, + }, + TargetPendingRequests: pointer.Int32(1234), + }, + } hdl := newForwardingHandler( logr.Discard(), routingTable, dialCtxFunc, waitFunc, - func(routing.Target) (*url.URL, error) { - return url.Parse("http://nosuchhost:9091") - }, forwardingConfig{ waitTimeout: timeouts.DeploymentReplicas, respHeaderTimeout: timeouts.ResponseHeader, @@ -203,7 +208,6 @@ func TestWaitsForWaitFunc(t *testing.T) { const ( noSuchHost = "TestWaitsForWaitFunc.test" originRespCode = 201 - namespace = "testns" ) testSrv, testSrvURL, err := kedanet.StartTestServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -212,24 +216,19 @@ func TestWaitsForWaitFunc(t *testing.T) { ) r.NoError(err) defer testSrv.Close() - originHost, originPort, err := splitHostPort(testSrvURL.Host) + _, originPort, err := splitHostPort(testSrvURL.Host) r.NoError(err) - routingTable := routing.NewTable() - r.NoError(routingTable.AddTarget(noSuchHost, routing.NewTarget( - namespace, - originHost, + routingTable := newTestRoutingTable() + routingTable.memory[noSuchHost] = targetFromURL( + testSrvURL, originPort, "nosuchdepl", - 1234, - ))) + ) hdl := newForwardingHandler( logr.Discard(), routingTable, dialCtxFunc, waitFunc, - func(routing.Target) (*url.URL, error) { - return testSrvURL, nil - }, forwardingConfig{ waitTimeout: timeouts.DeploymentReplicas, respHeaderTimeout: timeouts.ResponseHeader, @@ -288,23 +287,26 @@ func TestWaitHeaderTimeout(t *testing.T) { waitFunc := func(context.Context, string, string) (int, error) { return 1, nil } - routingTable := routing.NewTable() - target := routing.NewTarget( - "testns", - "testsvc", - 9094, - "testdepl", - 1234, - ) - r.NoError(routingTable.AddTarget(originURL.Host, target)) + routingTable := newTestRoutingTable() + target := &httpv1alpha1.HTTPScaledObject{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "testns", + }, + Spec: httpv1alpha1.HTTPScaledObjectSpec{ + ScaleTargetRef: &httpv1alpha1.ScaleTargetRef{ + Deployment: "nosuchdepl", + Service: "testsvc", + Port: 9094, + }, + TargetPendingRequests: pointer.Int32(1234), + }, + } + routingTable.memory[originURL.Host] = target hdl := newForwardingHandler( logr.Discard(), routingTable, dialCtxFunc, waitFunc, - func(routing.Target) (*url.URL, error) { - return originURL, nil - }, forwardingConfig{ waitTimeout: timeouts.DeploymentReplicas, respHeaderTimeout: timeouts.ResponseHeader, @@ -376,13 +378,19 @@ func targetFromURL( u *url.URL, port int, deployment string, -) routing.Target { - svc := strings.Split(u.Host, ":")[0] - return routing.NewTarget( - "testns", - svc, - port, - deployment, - 123, - ) +) *httpv1alpha1.HTTPScaledObject { + host := strings.Split(u.Host, ":")[0] + return &httpv1alpha1.HTTPScaledObject{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "@" + host, + }, + Spec: httpv1alpha1.HTTPScaledObjectSpec{ + ScaleTargetRef: &httpv1alpha1.ScaleTargetRef{ + Deployment: deployment, + Service: ":" + host, + Port: int32(port), + }, + TargetPendingRequests: pointer.Int32(123), + }, + } } diff --git a/operator/Dockerfile b/operator/Dockerfile index 23daa07e3..28474939f 100644 --- a/operator/Dockerfile +++ b/operator/Dockerfile @@ -1,36 +1,14 @@ -# Build the manager binary -FROM --platform=$BUILDPLATFORM ghcr.io/kedacore/build-tools:1.19.5 as builder -ARG VERSION=main -ARG GIT_COMMIT=HEAD - +FROM --platform=${BUILDPLATFORM} ghcr.io/kedacore/build-tools:1.20.4 as builder WORKDIR /workspace -# Copy the Go Modules manifests -COPY go.mod go.mod -COPY go.sum go.sum -# cache deps before building and copying source so that we don't need to re-download as much -# and so that source changes don't invalidate our downloaded layer +COPY go.* . RUN go mod download - -# Copy the go source -COPY operator operator -COPY pkg pkg -COPY proto proto -COPY Makefile Makefile - -# Build -# the ARCH has not a default value to allow the binary be built according to the host where the command -# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO -# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, -# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. +COPY . . +ARG VERSION=main +ARG GIT_COMMIT=HEAD ARG TARGETOS ARG TARGETARCH -RUN VERSION=${VERSION} GIT_COMMIT=${GIT_COMMIT} TARGET_OS=${TARGETOS:-linux} ARCH=${TARGETARCH} make build-operator +RUN VERSION="${VERSION}" GIT_COMMIT="${GIT_COMMIT}" TARGET_OS="${TARGETOS}" ARCH="${TARGETARCH}" make build-operator -# Use distroless as minimal base image to package the manager binary -# Refer to https://github.com/GoogleContainerTools/distroless for more details FROM gcr.io/distroless/static:nonroot -WORKDIR / -COPY --from=builder /workspace/bin/operator . -USER 65532:65532 - -ENTRYPOINT ["/operator"] +COPY --from=builder /workspace/bin/operator /sbin/init +ENTRYPOINT ["/sbin/init"] diff --git a/operator/controllers/http/app.go b/operator/controllers/http/app.go index ef1a0f187..ad66708e7 100644 --- a/operator/controllers/http/app.go +++ b/operator/controllers/http/app.go @@ -12,15 +12,12 @@ import ( "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1" "github.com/kedacore/http-add-on/operator/controllers/http/config" - "github.com/kedacore/http-add-on/pkg/routing" ) func removeApplicationResources( ctx context.Context, logger logr.Logger, cl client.Client, - routingTable *routing.Table, - baseConfig config.Base, httpso *v1alpha1.HTTPScaledObject, ) error { defer SaveStatus(context.Background(), logger, cl, httpso) @@ -49,7 +46,7 @@ func removeApplicationResources( // Delete App ScaledObject scaledObject := &unstructured.Unstructured{} scaledObject.SetNamespace(httpso.Namespace) - scaledObject.SetName(config.AppScaledObjectName(httpso)) + scaledObject.SetName(httpso.Name) scaledObject.SetGroupVersionKind(schema.GroupVersionKind{ Group: "keda.sh", Kind: "ScaledObject", @@ -80,17 +77,6 @@ func removeApplicationResources( v1alpha1.AppScaledObjectTerminated, )) - if err := removeAndUpdateRoutingTable( - ctx, - logger, - cl, - routingTable, - httpso.Spec.Host, - baseConfig.CurrentNamespace, - ); err != nil { - return err - } - return nil } @@ -98,7 +84,6 @@ func createOrUpdateApplicationResources( ctx context.Context, logger logr.Logger, cl client.Client, - routingTable *routing.Table, baseConfig config.Base, externalScalerConfig config.ExternalScaler, httpso *v1alpha1.HTTPScaledObject, @@ -129,37 +114,11 @@ func createOrUpdateApplicationResources( // the app deployment and the interceptor deployment. // this needs to be submitted so that KEDA will scale both the app and // interceptor - if err := createOrUpdateScaledObject( + return createOrUpdateScaledObject( ctx, cl, logger, externalScalerConfig.HostName(baseConfig.CurrentNamespace), httpso, - ); err != nil { - return err - } - - targetPendingReqs := baseConfig.TargetPendingRequests - if tpr := httpso.Spec.TargetPendingRequests; tpr != nil { - targetPendingReqs = *tpr - } - - if err := addAndUpdateRoutingTable( - ctx, - logger, - cl, - routingTable, - httpso.Spec.Host, - routing.NewTarget( - httpso.GetNamespace(), - httpso.Spec.ScaleTargetRef.Service, - int(httpso.Spec.ScaleTargetRef.Port), - httpso.Spec.ScaleTargetRef.Deployment, - targetPendingReqs, - ), - baseConfig.CurrentNamespace, - ); err != nil { - return err - } - return nil + ) } diff --git a/operator/controllers/http/config/app_info.go b/operator/controllers/http/config/app_info.go deleted file mode 100644 index 3d4eff3ec..000000000 --- a/operator/controllers/http/config/app_info.go +++ /dev/null @@ -1,13 +0,0 @@ -package config - -import ( - "fmt" - - "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1" -) - -// AppScaledObjectName returns the name of the ScaledObject -// that should be created alongside the given HTTPScaledObject. -func AppScaledObjectName(httpso *v1alpha1.HTTPScaledObject) string { - return fmt.Sprintf("%s-app", httpso.Spec.ScaleTargetRef.Deployment) -} diff --git a/operator/controllers/http/config/app_info_test.go b/operator/controllers/http/config/app_info_test.go deleted file mode 100644 index 7e7da558b..000000000 --- a/operator/controllers/http/config/app_info_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package config - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1" -) - -func TestAppScaledObjectName(t *testing.T) { - r := require.New(t) - obj := &v1alpha1.HTTPScaledObject{ - Spec: v1alpha1.HTTPScaledObjectSpec{ - ScaleTargetRef: &v1alpha1.ScaleTargetRef{ - Deployment: "TestAppScaledObjectNameDeployment", - }, - }, - } - name := AppScaledObjectName(obj) - r.Equal( - fmt.Sprintf( - "%s-app", - obj.Spec.ScaleTargetRef.Deployment, - ), - name, - ) -} diff --git a/operator/controllers/http/httpscaledobject_controller.go b/operator/controllers/http/httpscaledobject_controller.go index 01381d359..4849dc3a2 100644 --- a/operator/controllers/http/httpscaledobject_controller.go +++ b/operator/controllers/http/httpscaledobject_controller.go @@ -31,7 +31,6 @@ import ( httpv1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1" "github.com/kedacore/http-add-on/operator/controllers/http/config" - "github.com/kedacore/http-add-on/pkg/routing" ) // HTTPScaledObjectReconciler reconciles a HTTPScaledObject object @@ -45,7 +44,6 @@ type HTTPScaledObjectReconciler struct { InterceptorConfig config.Interceptor ExternalScalerConfig config.ExternalScaler BaseConfig config.Base - RoutingTable *routing.Table } // +kubebuilder:rbac:groups=http.keda.sh,resources=httpscaledobjects,verbs=get;list;watch;create;update;patch;delete @@ -87,8 +85,6 @@ func (r *HTTPScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.Req ctx, logger, r.Client, - r.RoutingTable, - r.BaseConfig, httpso, ) if removeErr != nil { @@ -122,7 +118,6 @@ func (r *HTTPScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.Req ctx, logger, r.Client, - r.RoutingTable, r.BaseConfig, r.ExternalScalerConfig, httpso, @@ -133,8 +128,6 @@ func (r *HTTPScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.Req ctx, logger, r.Client, - r.RoutingTable, - r.BaseConfig, httpso, ); removeErr != nil { logger.Error(removeErr, "Removing previously created resources") diff --git a/operator/controllers/http/routing_table.go b/operator/controllers/http/routing_table.go deleted file mode 100644 index e50c16842..000000000 --- a/operator/controllers/http/routing_table.go +++ /dev/null @@ -1,80 +0,0 @@ -package http - -import ( - "context" - - "github.com/go-logr/logr" - pkgerrs "github.com/pkg/errors" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/kedacore/http-add-on/pkg/k8s" - "github.com/kedacore/http-add-on/pkg/routing" -) - -func removeAndUpdateRoutingTable( - ctx context.Context, - lggr logr.Logger, - cl client.Client, - table *routing.Table, - host, - namespace string, -) error { - lggr = lggr.WithName("removeAndUpdateRoutingTable") - if err := table.RemoveTarget(host); err != nil { - lggr.Error( - err, - "could not remove host from routing table, progressing anyway", - "host", - host, - ) - } - - return updateRoutingMap(ctx, lggr, cl, namespace, table) -} - -func addAndUpdateRoutingTable( - ctx context.Context, - lggr logr.Logger, - cl client.Client, - table *routing.Table, - host string, - target routing.Target, - namespace string, -) error { - lggr = lggr.WithName("addAndUpdateRoutingTable") - if err := table.AddTarget(host, target); err != nil { - lggr.Error( - err, - "could not add host to routing table, progressing anyway", - "host", - host, - ) - } - return updateRoutingMap(ctx, lggr, cl, namespace, table) -} - -func updateRoutingMap( - ctx context.Context, - lggr logr.Logger, - cl client.Client, - namespace string, - table *routing.Table, -) error { - lggr = lggr.WithName("updateRoutingMap") - routingConfigMap, err := k8s.GetConfigMap(ctx, cl, namespace, routing.ConfigMapRoutingTableName) - if err != nil { - lggr.Error(err, "Error getting configmap", "configMapName", routing.ConfigMapRoutingTableName) - return pkgerrs.Wrap(err, "routing table ConfigMap fetch error") - } - newCM := routingConfigMap.DeepCopy() - if err := routing.SaveTableToConfigMap(table, newCM); err != nil { - lggr.Error(err, "couldn't save new routing table to ConfigMap", "configMap", routing.ConfigMapRoutingTableName) - return pkgerrs.Wrap(err, "ConfigMap save error") - } - if _, err := k8s.PatchConfigMap(ctx, lggr, cl, routingConfigMap, newCM); err != nil { - lggr.Error(err, "couldn't save new routing table ConfigMap to Kubernetes", "configMap", routing.ConfigMapRoutingTableName) - return pkgerrs.Wrap(err, "saving routing table ConfigMap to Kubernetes") - } - - return nil -} diff --git a/operator/controllers/http/routing_table_test.go b/operator/controllers/http/routing_table_test.go deleted file mode 100644 index 07d6bf14e..000000000 --- a/operator/controllers/http/routing_table_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package http - -import ( - "context" - "testing" - - "github.com/go-logr/logr" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/kedacore/http-add-on/pkg/k8s" - "github.com/kedacore/http-add-on/pkg/routing" -) - -func TestRoutingTable(t *testing.T) { - table := routing.NewTable() - const ( - host = "myhost.com" - ns = "testns" - svcName = "testsvc" - deplName = "testdepl" - ) - r := require.New(t) - ctx := context.Background() - cl := k8s.NewFakeRuntimeClient() - cm := &corev1.ConfigMap{ - Data: map[string]string{}, - } - // save the empty routing table to the config map, - // so that it has valid structure - r.NoError(routing.SaveTableToConfigMap(table, cm)) - cl.GetFunc = func() client.Object { - return cm - } - target := routing.NewTarget( - ns, - svcName, - 8080, - deplName, - 1234, - ) - r.NoError(addAndUpdateRoutingTable( - ctx, - logr.Discard(), - cl, - table, - host, - target, - ns, - )) - // ensure that the ConfigMap was read and created. no updates - // should occur - r.Equal(1, len(cl.FakeRuntimeClientReader.GetCalls)) - r.Equal(1, len(cl.FakeRuntimeClientWriter.Patches)) - r.Equal(0, len(cl.FakeRuntimeClientWriter.Updates)) - r.Equal(0, len(cl.FakeRuntimeClientWriter.Creates)) - - retTarget, err := table.Lookup(host) - r.NoError(err) - r.Equal(&target, retTarget) - - r.NoError(removeAndUpdateRoutingTable( - ctx, - logr.Discard(), - cl, - table, - host, - ns, - )) - - // ensure that the ConfigMap was read and updated. no additional - // creates should occur. - r.Equal(2, len(cl.FakeRuntimeClientReader.GetCalls)) - r.Equal(2, len(cl.FakeRuntimeClientWriter.Patches)) - r.Equal(0, len(cl.FakeRuntimeClientWriter.Updates)) - r.Equal(0, len(cl.FakeRuntimeClientWriter.Creates)) - - _, err = table.Lookup(host) - r.Error(err) -} diff --git a/operator/controllers/http/scaled_object.go b/operator/controllers/http/scaled_object.go index afa9aed33..668a2b1e4 100644 --- a/operator/controllers/http/scaled_object.go +++ b/operator/controllers/http/scaled_object.go @@ -10,7 +10,7 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1" + httpv1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1" "github.com/kedacore/http-add-on/pkg/k8s" ) @@ -23,7 +23,7 @@ func createOrUpdateScaledObject( cl client.Client, logger logr.Logger, externalScalerHostName string, - httpso *v1alpha1.HTTPScaledObject, + httpso *httpv1alpha1.HTTPScaledObject, ) error { logger.Info("Creating scaled objects", "external scaler host name", externalScalerHostName) @@ -36,7 +36,7 @@ func createOrUpdateScaledObject( appScaledObject := k8s.NewScaledObject( httpso.GetNamespace(), - fmt.Sprintf("%s-app", httpso.GetName()), // HTTPScaledObject name is the same as the ScaledObject name + httpso.GetName(), // HTTPScaledObject name is the same as the ScaledObject name httpso.Spec.ScaleTargetRef.Deployment, externalScalerHostName, httpso.Spec.Host, @@ -72,9 +72,9 @@ func createOrUpdateScaledObject( httpso, *SetMessage( CreateCondition( - v1alpha1.Error, + httpv1alpha1.Error, v1.ConditionFalse, - v1alpha1.ErrorCreatingAppScaledObject, + httpv1alpha1.ErrorCreatingAppScaledObject, ), err.Error(), ), @@ -89,13 +89,40 @@ func createOrUpdateScaledObject( httpso, *SetMessage( CreateCondition( - v1alpha1.Created, + httpv1alpha1.Created, v1.ConditionTrue, - v1alpha1.AppScaledObjectCreated, + httpv1alpha1.AppScaledObjectCreated, ), "App ScaledObject created", ), ) + return purgeLegacySO(ctx, cl, logger, httpso) +} + +// TODO(pedrotorres): delete this on v0.6.0 +func purgeLegacySO( + ctx context.Context, + cl client.Client, + logger logr.Logger, + httpso *httpv1alpha1.HTTPScaledObject, +) error { + legacyName := fmt.Sprintf("%s-app", httpso.GetName()) + legacyKey := client.ObjectKey{ + Namespace: httpso.GetNamespace(), + Name: legacyName, + } + + var legacySO kedav1alpha1.ScaledObject + if err := cl.Get(ctx, legacyKey, &legacySO); err != nil && errors.IsNotFound(err) { + logger.Error(err, "failed getting legacy ScaledObject") + return err + } + + if err := cl.Delete(ctx, &legacySO); err != nil && errors.IsNotFound(err) { + logger.Error(err, "failed deleting legacy ScaledObject") + return err + } + return nil } diff --git a/operator/controllers/http/scaled_object_test.go b/operator/controllers/http/scaled_object_test.go index 25d583bcd..d6ba7e5ff 100644 --- a/operator/controllers/http/scaled_object_test.go +++ b/operator/controllers/http/scaled_object_test.go @@ -11,7 +11,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1" - "github.com/kedacore/http-add-on/operator/controllers/http/config" ) func TestCreateOrUpdateScaledObject(t *testing.T) { @@ -52,7 +51,7 @@ func TestCreateOrUpdateScaledObject(t *testing.T) { r.Equal(testInfra.ns, metadata.Namespace) r.Equal( - config.AppScaledObjectName(&testInfra.httpso), + testInfra.httpso.GetName(), metadata.Name, ) @@ -117,7 +116,7 @@ func getSO( var retSO kedav1alpha1.ScaledObject err := cl.Get(ctx, client.ObjectKey{ Namespace: httpso.GetNamespace(), - Name: config.AppScaledObjectName(&httpso), + Name: httpso.GetName(), }, &retSO) return &retSO, err } diff --git a/operator/generated/clientset/versioned/mock/clientset.go b/operator/generated/clientset/versioned/mock/clientset.go new file mode 100644 index 000000000..6b7be9af2 --- /dev/null +++ b/operator/generated/clientset/versioned/mock/clientset.go @@ -0,0 +1,81 @@ +// /* +// Copyright 2023 The KEDA Authors. +// +// Licensed 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. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: operator/generated/clientset/versioned/clientset.go + +// Package mock is a generated GoMock package. +package mock + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + v1alpha1 "github.com/kedacore/http-add-on/operator/generated/clientset/versioned/typed/http/v1alpha1" + discovery "k8s.io/client-go/discovery" +) + +// MockInterface is a mock of Interface interface. +type MockInterface struct { + ctrl *gomock.Controller + recorder *MockInterfaceMockRecorder +} + +// MockInterfaceMockRecorder is the mock recorder for MockInterface. +type MockInterfaceMockRecorder struct { + mock *MockInterface +} + +// NewMockInterface creates a new mock instance. +func NewMockInterface(ctrl *gomock.Controller) *MockInterface { + mock := &MockInterface{ctrl: ctrl} + mock.recorder = &MockInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder { + return m.recorder +} + +// Discovery mocks base method. +func (m *MockInterface) Discovery() discovery.DiscoveryInterface { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Discovery") + ret0, _ := ret[0].(discovery.DiscoveryInterface) + return ret0 +} + +// Discovery indicates an expected call of Discovery. +func (mr *MockInterfaceMockRecorder) Discovery() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Discovery", reflect.TypeOf((*MockInterface)(nil).Discovery)) +} + +// HttpV1alpha1 mocks base method. +func (m *MockInterface) HttpV1alpha1() v1alpha1.HttpV1alpha1Interface { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HttpV1alpha1") + ret0, _ := ret[0].(v1alpha1.HttpV1alpha1Interface) + return ret0 +} + +// HttpV1alpha1 indicates an expected call of HttpV1alpha1. +func (mr *MockInterfaceMockRecorder) HttpV1alpha1() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HttpV1alpha1", reflect.TypeOf((*MockInterface)(nil).HttpV1alpha1)) +} diff --git a/operator/generated/clientset/versioned/mock/doc.go b/operator/generated/clientset/versioned/mock/doc.go new file mode 100644 index 000000000..c0249edb6 --- /dev/null +++ b/operator/generated/clientset/versioned/mock/doc.go @@ -0,0 +1,22 @@ +// /* +// Copyright 2023 The KEDA Authors. +// +// Licensed 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. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: operator/generated/clientset/versioned/doc.go + +// Package mock is a generated GoMock package. +package mock diff --git a/operator/generated/clientset/versioned/scheme/mock/doc.go b/operator/generated/clientset/versioned/scheme/mock/doc.go new file mode 100644 index 000000000..4aa8810bb --- /dev/null +++ b/operator/generated/clientset/versioned/scheme/mock/doc.go @@ -0,0 +1,22 @@ +// /* +// Copyright 2023 The KEDA Authors. +// +// Licensed 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. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: operator/generated/clientset/versioned/scheme/doc.go + +// Package mock is a generated GoMock package. +package mock diff --git a/operator/generated/clientset/versioned/scheme/mock/register.go b/operator/generated/clientset/versioned/scheme/mock/register.go new file mode 100644 index 000000000..1129b6cd2 --- /dev/null +++ b/operator/generated/clientset/versioned/scheme/mock/register.go @@ -0,0 +1,22 @@ +// /* +// Copyright 2023 The KEDA Authors. +// +// Licensed 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. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: operator/generated/clientset/versioned/scheme/register.go + +// Package mock is a generated GoMock package. +package mock diff --git a/operator/generated/clientset/versioned/typed/http/v1alpha1/mock/doc.go b/operator/generated/clientset/versioned/typed/http/v1alpha1/mock/doc.go new file mode 100644 index 000000000..557296cdf --- /dev/null +++ b/operator/generated/clientset/versioned/typed/http/v1alpha1/mock/doc.go @@ -0,0 +1,22 @@ +// /* +// Copyright 2023 The KEDA Authors. +// +// Licensed 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. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: operator/generated/clientset/versioned/typed/http/v1alpha1/doc.go + +// Package mock is a generated GoMock package. +package mock diff --git a/operator/generated/clientset/versioned/typed/http/v1alpha1/mock/generated_expansion.go b/operator/generated/clientset/versioned/typed/http/v1alpha1/mock/generated_expansion.go new file mode 100644 index 000000000..22c4cb49f --- /dev/null +++ b/operator/generated/clientset/versioned/typed/http/v1alpha1/mock/generated_expansion.go @@ -0,0 +1,49 @@ +// /* +// Copyright 2023 The KEDA Authors. +// +// Licensed 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. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: operator/generated/clientset/versioned/typed/http/v1alpha1/generated_expansion.go + +// Package mock is a generated GoMock package. +package mock + +import ( + gomock "github.com/golang/mock/gomock" +) + +// MockHTTPScaledObjectExpansion is a mock of HTTPScaledObjectExpansion interface. +type MockHTTPScaledObjectExpansion struct { + ctrl *gomock.Controller + recorder *MockHTTPScaledObjectExpansionMockRecorder +} + +// MockHTTPScaledObjectExpansionMockRecorder is the mock recorder for MockHTTPScaledObjectExpansion. +type MockHTTPScaledObjectExpansionMockRecorder struct { + mock *MockHTTPScaledObjectExpansion +} + +// NewMockHTTPScaledObjectExpansion creates a new mock instance. +func NewMockHTTPScaledObjectExpansion(ctrl *gomock.Controller) *MockHTTPScaledObjectExpansion { + mock := &MockHTTPScaledObjectExpansion{ctrl: ctrl} + mock.recorder = &MockHTTPScaledObjectExpansionMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHTTPScaledObjectExpansion) EXPECT() *MockHTTPScaledObjectExpansionMockRecorder { + return m.recorder +} diff --git a/operator/generated/clientset/versioned/typed/http/v1alpha1/mock/http_client.go b/operator/generated/clientset/versioned/typed/http/v1alpha1/mock/http_client.go new file mode 100644 index 000000000..ffcf5ae00 --- /dev/null +++ b/operator/generated/clientset/versioned/typed/http/v1alpha1/mock/http_client.go @@ -0,0 +1,81 @@ +// /* +// Copyright 2023 The KEDA Authors. +// +// Licensed 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. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: operator/generated/clientset/versioned/typed/http/v1alpha1/http_client.go + +// Package mock is a generated GoMock package. +package mock + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + v1alpha1 "github.com/kedacore/http-add-on/operator/generated/clientset/versioned/typed/http/v1alpha1" + rest "k8s.io/client-go/rest" +) + +// MockHttpV1alpha1Interface is a mock of HttpV1alpha1Interface interface. +type MockHttpV1alpha1Interface struct { + ctrl *gomock.Controller + recorder *MockHttpV1alpha1InterfaceMockRecorder +} + +// MockHttpV1alpha1InterfaceMockRecorder is the mock recorder for MockHttpV1alpha1Interface. +type MockHttpV1alpha1InterfaceMockRecorder struct { + mock *MockHttpV1alpha1Interface +} + +// NewMockHttpV1alpha1Interface creates a new mock instance. +func NewMockHttpV1alpha1Interface(ctrl *gomock.Controller) *MockHttpV1alpha1Interface { + mock := &MockHttpV1alpha1Interface{ctrl: ctrl} + mock.recorder = &MockHttpV1alpha1InterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHttpV1alpha1Interface) EXPECT() *MockHttpV1alpha1InterfaceMockRecorder { + return m.recorder +} + +// HTTPScaledObjects mocks base method. +func (m *MockHttpV1alpha1Interface) HTTPScaledObjects(namespace string) v1alpha1.HTTPScaledObjectInterface { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HTTPScaledObjects", namespace) + ret0, _ := ret[0].(v1alpha1.HTTPScaledObjectInterface) + return ret0 +} + +// HTTPScaledObjects indicates an expected call of HTTPScaledObjects. +func (mr *MockHttpV1alpha1InterfaceMockRecorder) HTTPScaledObjects(namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HTTPScaledObjects", reflect.TypeOf((*MockHttpV1alpha1Interface)(nil).HTTPScaledObjects), namespace) +} + +// RESTClient mocks base method. +func (m *MockHttpV1alpha1Interface) RESTClient() rest.Interface { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RESTClient") + ret0, _ := ret[0].(rest.Interface) + return ret0 +} + +// RESTClient indicates an expected call of RESTClient. +func (mr *MockHttpV1alpha1InterfaceMockRecorder) RESTClient() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RESTClient", reflect.TypeOf((*MockHttpV1alpha1Interface)(nil).RESTClient)) +} diff --git a/operator/generated/clientset/versioned/typed/http/v1alpha1/mock/httpscaledobject.go b/operator/generated/clientset/versioned/typed/http/v1alpha1/mock/httpscaledobject.go new file mode 100644 index 000000000..bb802bd2c --- /dev/null +++ b/operator/generated/clientset/versioned/typed/http/v1alpha1/mock/httpscaledobject.go @@ -0,0 +1,232 @@ +// /* +// Copyright 2023 The KEDA Authors. +// +// Licensed 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. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: operator/generated/clientset/versioned/typed/http/v1alpha1/httpscaledobject.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + v1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1" + v1alpha10 "github.com/kedacore/http-add-on/operator/generated/clientset/versioned/typed/http/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" +) + +// MockHTTPScaledObjectsGetter is a mock of HTTPScaledObjectsGetter interface. +type MockHTTPScaledObjectsGetter struct { + ctrl *gomock.Controller + recorder *MockHTTPScaledObjectsGetterMockRecorder +} + +// MockHTTPScaledObjectsGetterMockRecorder is the mock recorder for MockHTTPScaledObjectsGetter. +type MockHTTPScaledObjectsGetterMockRecorder struct { + mock *MockHTTPScaledObjectsGetter +} + +// NewMockHTTPScaledObjectsGetter creates a new mock instance. +func NewMockHTTPScaledObjectsGetter(ctrl *gomock.Controller) *MockHTTPScaledObjectsGetter { + mock := &MockHTTPScaledObjectsGetter{ctrl: ctrl} + mock.recorder = &MockHTTPScaledObjectsGetterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHTTPScaledObjectsGetter) EXPECT() *MockHTTPScaledObjectsGetterMockRecorder { + return m.recorder +} + +// HTTPScaledObjects mocks base method. +func (m *MockHTTPScaledObjectsGetter) HTTPScaledObjects(namespace string) v1alpha10.HTTPScaledObjectInterface { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HTTPScaledObjects", namespace) + ret0, _ := ret[0].(v1alpha10.HTTPScaledObjectInterface) + return ret0 +} + +// HTTPScaledObjects indicates an expected call of HTTPScaledObjects. +func (mr *MockHTTPScaledObjectsGetterMockRecorder) HTTPScaledObjects(namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HTTPScaledObjects", reflect.TypeOf((*MockHTTPScaledObjectsGetter)(nil).HTTPScaledObjects), namespace) +} + +// MockHTTPScaledObjectInterface is a mock of HTTPScaledObjectInterface interface. +type MockHTTPScaledObjectInterface struct { + ctrl *gomock.Controller + recorder *MockHTTPScaledObjectInterfaceMockRecorder +} + +// MockHTTPScaledObjectInterfaceMockRecorder is the mock recorder for MockHTTPScaledObjectInterface. +type MockHTTPScaledObjectInterfaceMockRecorder struct { + mock *MockHTTPScaledObjectInterface +} + +// NewMockHTTPScaledObjectInterface creates a new mock instance. +func NewMockHTTPScaledObjectInterface(ctrl *gomock.Controller) *MockHTTPScaledObjectInterface { + mock := &MockHTTPScaledObjectInterface{ctrl: ctrl} + mock.recorder = &MockHTTPScaledObjectInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHTTPScaledObjectInterface) EXPECT() *MockHTTPScaledObjectInterfaceMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockHTTPScaledObjectInterface) Create(ctx context.Context, hTTPScaledObject *v1alpha1.HTTPScaledObject, opts v1.CreateOptions) (*v1alpha1.HTTPScaledObject, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", ctx, hTTPScaledObject, opts) + ret0, _ := ret[0].(*v1alpha1.HTTPScaledObject) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Create indicates an expected call of Create. +func (mr *MockHTTPScaledObjectInterfaceMockRecorder) Create(ctx, hTTPScaledObject, opts interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockHTTPScaledObjectInterface)(nil).Create), ctx, hTTPScaledObject, opts) +} + +// Delete mocks base method. +func (m *MockHTTPScaledObjectInterface) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", ctx, name, opts) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockHTTPScaledObjectInterfaceMockRecorder) Delete(ctx, name, opts interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockHTTPScaledObjectInterface)(nil).Delete), ctx, name, opts) +} + +// DeleteCollection mocks base method. +func (m *MockHTTPScaledObjectInterface) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteCollection", ctx, opts, listOpts) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteCollection indicates an expected call of DeleteCollection. +func (mr *MockHTTPScaledObjectInterfaceMockRecorder) DeleteCollection(ctx, opts, listOpts interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCollection", reflect.TypeOf((*MockHTTPScaledObjectInterface)(nil).DeleteCollection), ctx, opts, listOpts) +} + +// Get mocks base method. +func (m *MockHTTPScaledObjectInterface) Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.HTTPScaledObject, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", ctx, name, opts) + ret0, _ := ret[0].(*v1alpha1.HTTPScaledObject) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockHTTPScaledObjectInterfaceMockRecorder) Get(ctx, name, opts interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockHTTPScaledObjectInterface)(nil).Get), ctx, name, opts) +} + +// List mocks base method. +func (m *MockHTTPScaledObjectInterface) List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.HTTPScaledObjectList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "List", ctx, opts) + ret0, _ := ret[0].(*v1alpha1.HTTPScaledObjectList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockHTTPScaledObjectInterfaceMockRecorder) List(ctx, opts interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockHTTPScaledObjectInterface)(nil).List), ctx, opts) +} + +// Patch mocks base method. +func (m *MockHTTPScaledObjectInterface) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (*v1alpha1.HTTPScaledObject, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, name, pt, data, opts} + for _, a := range subresources { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Patch", varargs...) + ret0, _ := ret[0].(*v1alpha1.HTTPScaledObject) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Patch indicates an expected call of Patch. +func (mr *MockHTTPScaledObjectInterfaceMockRecorder) Patch(ctx, name, pt, data, opts interface{}, subresources ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, name, pt, data, opts}, subresources...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockHTTPScaledObjectInterface)(nil).Patch), varargs...) +} + +// Update mocks base method. +func (m *MockHTTPScaledObjectInterface) Update(ctx context.Context, hTTPScaledObject *v1alpha1.HTTPScaledObject, opts v1.UpdateOptions) (*v1alpha1.HTTPScaledObject, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update", ctx, hTTPScaledObject, opts) + ret0, _ := ret[0].(*v1alpha1.HTTPScaledObject) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Update indicates an expected call of Update. +func (mr *MockHTTPScaledObjectInterfaceMockRecorder) Update(ctx, hTTPScaledObject, opts interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockHTTPScaledObjectInterface)(nil).Update), ctx, hTTPScaledObject, opts) +} + +// UpdateStatus mocks base method. +func (m *MockHTTPScaledObjectInterface) UpdateStatus(ctx context.Context, hTTPScaledObject *v1alpha1.HTTPScaledObject, opts v1.UpdateOptions) (*v1alpha1.HTTPScaledObject, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateStatus", ctx, hTTPScaledObject, opts) + ret0, _ := ret[0].(*v1alpha1.HTTPScaledObject) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateStatus indicates an expected call of UpdateStatus. +func (mr *MockHTTPScaledObjectInterfaceMockRecorder) UpdateStatus(ctx, hTTPScaledObject, opts interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateStatus", reflect.TypeOf((*MockHTTPScaledObjectInterface)(nil).UpdateStatus), ctx, hTTPScaledObject, opts) +} + +// Watch mocks base method. +func (m *MockHTTPScaledObjectInterface) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Watch", ctx, opts) + ret0, _ := ret[0].(watch.Interface) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Watch indicates an expected call of Watch. +func (mr *MockHTTPScaledObjectInterfaceMockRecorder) Watch(ctx, opts interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Watch", reflect.TypeOf((*MockHTTPScaledObjectInterface)(nil).Watch), ctx, opts) +} diff --git a/operator/generated/informers/externalversions/http/mock/interface.go b/operator/generated/informers/externalversions/http/mock/interface.go new file mode 100644 index 000000000..6993af756 --- /dev/null +++ b/operator/generated/informers/externalversions/http/mock/interface.go @@ -0,0 +1,66 @@ +// /* +// Copyright 2023 The KEDA Authors. +// +// Licensed 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. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: operator/generated/informers/externalversions/http/interface.go + +// Package mock is a generated GoMock package. +package mock + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + v1alpha1 "github.com/kedacore/http-add-on/operator/generated/informers/externalversions/http/v1alpha1" +) + +// MockInterface is a mock of Interface interface. +type MockInterface struct { + ctrl *gomock.Controller + recorder *MockInterfaceMockRecorder +} + +// MockInterfaceMockRecorder is the mock recorder for MockInterface. +type MockInterfaceMockRecorder struct { + mock *MockInterface +} + +// NewMockInterface creates a new mock instance. +func NewMockInterface(ctrl *gomock.Controller) *MockInterface { + mock := &MockInterface{ctrl: ctrl} + mock.recorder = &MockInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder { + return m.recorder +} + +// V1alpha1 mocks base method. +func (m *MockInterface) V1alpha1() v1alpha1.Interface { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "V1alpha1") + ret0, _ := ret[0].(v1alpha1.Interface) + return ret0 +} + +// V1alpha1 indicates an expected call of V1alpha1. +func (mr *MockInterfaceMockRecorder) V1alpha1() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "V1alpha1", reflect.TypeOf((*MockInterface)(nil).V1alpha1)) +} diff --git a/operator/generated/informers/externalversions/http/v1alpha1/mock/httpscaledobject.go b/operator/generated/informers/externalversions/http/v1alpha1/mock/httpscaledobject.go new file mode 100644 index 000000000..985e8d58c --- /dev/null +++ b/operator/generated/informers/externalversions/http/v1alpha1/mock/httpscaledobject.go @@ -0,0 +1,81 @@ +// /* +// Copyright 2023 The KEDA Authors. +// +// Licensed 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. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: operator/generated/informers/externalversions/http/v1alpha1/httpscaledobject.go + +// Package mock is a generated GoMock package. +package mock + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + v1alpha1 "github.com/kedacore/http-add-on/operator/generated/listers/http/v1alpha1" + cache "k8s.io/client-go/tools/cache" +) + +// MockHTTPScaledObjectInformer is a mock of HTTPScaledObjectInformer interface. +type MockHTTPScaledObjectInformer struct { + ctrl *gomock.Controller + recorder *MockHTTPScaledObjectInformerMockRecorder +} + +// MockHTTPScaledObjectInformerMockRecorder is the mock recorder for MockHTTPScaledObjectInformer. +type MockHTTPScaledObjectInformerMockRecorder struct { + mock *MockHTTPScaledObjectInformer +} + +// NewMockHTTPScaledObjectInformer creates a new mock instance. +func NewMockHTTPScaledObjectInformer(ctrl *gomock.Controller) *MockHTTPScaledObjectInformer { + mock := &MockHTTPScaledObjectInformer{ctrl: ctrl} + mock.recorder = &MockHTTPScaledObjectInformerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHTTPScaledObjectInformer) EXPECT() *MockHTTPScaledObjectInformerMockRecorder { + return m.recorder +} + +// Informer mocks base method. +func (m *MockHTTPScaledObjectInformer) Informer() cache.SharedIndexInformer { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Informer") + ret0, _ := ret[0].(cache.SharedIndexInformer) + return ret0 +} + +// Informer indicates an expected call of Informer. +func (mr *MockHTTPScaledObjectInformerMockRecorder) Informer() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Informer", reflect.TypeOf((*MockHTTPScaledObjectInformer)(nil).Informer)) +} + +// Lister mocks base method. +func (m *MockHTTPScaledObjectInformer) Lister() v1alpha1.HTTPScaledObjectLister { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Lister") + ret0, _ := ret[0].(v1alpha1.HTTPScaledObjectLister) + return ret0 +} + +// Lister indicates an expected call of Lister. +func (mr *MockHTTPScaledObjectInformerMockRecorder) Lister() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lister", reflect.TypeOf((*MockHTTPScaledObjectInformer)(nil).Lister)) +} diff --git a/operator/generated/informers/externalversions/http/v1alpha1/mock/interface.go b/operator/generated/informers/externalversions/http/v1alpha1/mock/interface.go new file mode 100644 index 000000000..50aed1959 --- /dev/null +++ b/operator/generated/informers/externalversions/http/v1alpha1/mock/interface.go @@ -0,0 +1,66 @@ +// /* +// Copyright 2023 The KEDA Authors. +// +// Licensed 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. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: operator/generated/informers/externalversions/http/v1alpha1/interface.go + +// Package mock is a generated GoMock package. +package mock + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + v1alpha1 "github.com/kedacore/http-add-on/operator/generated/informers/externalversions/http/v1alpha1" +) + +// MockInterface is a mock of Interface interface. +type MockInterface struct { + ctrl *gomock.Controller + recorder *MockInterfaceMockRecorder +} + +// MockInterfaceMockRecorder is the mock recorder for MockInterface. +type MockInterfaceMockRecorder struct { + mock *MockInterface +} + +// NewMockInterface creates a new mock instance. +func NewMockInterface(ctrl *gomock.Controller) *MockInterface { + mock := &MockInterface{ctrl: ctrl} + mock.recorder = &MockInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder { + return m.recorder +} + +// HTTPScaledObjects mocks base method. +func (m *MockInterface) HTTPScaledObjects() v1alpha1.HTTPScaledObjectInformer { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HTTPScaledObjects") + ret0, _ := ret[0].(v1alpha1.HTTPScaledObjectInformer) + return ret0 +} + +// HTTPScaledObjects indicates an expected call of HTTPScaledObjects. +func (mr *MockInterfaceMockRecorder) HTTPScaledObjects() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HTTPScaledObjects", reflect.TypeOf((*MockInterface)(nil).HTTPScaledObjects)) +} diff --git a/operator/generated/informers/externalversions/internalinterfaces/mock/factory_interfaces.go b/operator/generated/informers/externalversions/internalinterfaces/mock/factory_interfaces.go new file mode 100644 index 000000000..05417d055 --- /dev/null +++ b/operator/generated/informers/externalversions/internalinterfaces/mock/factory_interfaces.go @@ -0,0 +1,80 @@ +// /* +// Copyright 2023 The KEDA Authors. +// +// Licensed 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. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: operator/generated/informers/externalversions/internalinterfaces/factory_interfaces.go + +// Package mock is a generated GoMock package. +package mock + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + internalinterfaces "github.com/kedacore/http-add-on/operator/generated/informers/externalversions/internalinterfaces" + runtime "k8s.io/apimachinery/pkg/runtime" + cache "k8s.io/client-go/tools/cache" +) + +// MockSharedInformerFactory is a mock of SharedInformerFactory interface. +type MockSharedInformerFactory struct { + ctrl *gomock.Controller + recorder *MockSharedInformerFactoryMockRecorder +} + +// MockSharedInformerFactoryMockRecorder is the mock recorder for MockSharedInformerFactory. +type MockSharedInformerFactoryMockRecorder struct { + mock *MockSharedInformerFactory +} + +// NewMockSharedInformerFactory creates a new mock instance. +func NewMockSharedInformerFactory(ctrl *gomock.Controller) *MockSharedInformerFactory { + mock := &MockSharedInformerFactory{ctrl: ctrl} + mock.recorder = &MockSharedInformerFactoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSharedInformerFactory) EXPECT() *MockSharedInformerFactoryMockRecorder { + return m.recorder +} + +// InformerFor mocks base method. +func (m *MockSharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InformerFor", obj, newFunc) + ret0, _ := ret[0].(cache.SharedIndexInformer) + return ret0 +} + +// InformerFor indicates an expected call of InformerFor. +func (mr *MockSharedInformerFactoryMockRecorder) InformerFor(obj, newFunc interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InformerFor", reflect.TypeOf((*MockSharedInformerFactory)(nil).InformerFor), obj, newFunc) +} + +// Start mocks base method. +func (m *MockSharedInformerFactory) Start(stopCh <-chan struct{}) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Start", stopCh) +} + +// Start indicates an expected call of Start. +func (mr *MockSharedInformerFactoryMockRecorder) Start(stopCh interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockSharedInformerFactory)(nil).Start), stopCh) +} diff --git a/operator/generated/informers/externalversions/mock/factory.go b/operator/generated/informers/externalversions/mock/factory.go new file mode 100644 index 000000000..57db36fec --- /dev/null +++ b/operator/generated/informers/externalversions/mock/factory.go @@ -0,0 +1,138 @@ +// /* +// Copyright 2023 The KEDA Authors. +// +// Licensed 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. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: operator/generated/informers/externalversions/factory.go + +// Package mock is a generated GoMock package. +package mock + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + externalversions "github.com/kedacore/http-add-on/operator/generated/informers/externalversions" + http "github.com/kedacore/http-add-on/operator/generated/informers/externalversions/http" + internalinterfaces "github.com/kedacore/http-add-on/operator/generated/informers/externalversions/internalinterfaces" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + cache "k8s.io/client-go/tools/cache" +) + +// MockSharedInformerFactory is a mock of SharedInformerFactory interface. +type MockSharedInformerFactory struct { + ctrl *gomock.Controller + recorder *MockSharedInformerFactoryMockRecorder +} + +// MockSharedInformerFactoryMockRecorder is the mock recorder for MockSharedInformerFactory. +type MockSharedInformerFactoryMockRecorder struct { + mock *MockSharedInformerFactory +} + +// NewMockSharedInformerFactory creates a new mock instance. +func NewMockSharedInformerFactory(ctrl *gomock.Controller) *MockSharedInformerFactory { + mock := &MockSharedInformerFactory{ctrl: ctrl} + mock.recorder = &MockSharedInformerFactoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSharedInformerFactory) EXPECT() *MockSharedInformerFactoryMockRecorder { + return m.recorder +} + +// ForResource mocks base method. +func (m *MockSharedInformerFactory) ForResource(resource schema.GroupVersionResource) (externalversions.GenericInformer, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ForResource", resource) + ret0, _ := ret[0].(externalversions.GenericInformer) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ForResource indicates an expected call of ForResource. +func (mr *MockSharedInformerFactoryMockRecorder) ForResource(resource interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ForResource", reflect.TypeOf((*MockSharedInformerFactory)(nil).ForResource), resource) +} + +// Http mocks base method. +func (m *MockSharedInformerFactory) Http() http.Interface { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Http") + ret0, _ := ret[0].(http.Interface) + return ret0 +} + +// Http indicates an expected call of Http. +func (mr *MockSharedInformerFactoryMockRecorder) Http() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Http", reflect.TypeOf((*MockSharedInformerFactory)(nil).Http)) +} + +// InformerFor mocks base method. +func (m *MockSharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InformerFor", obj, newFunc) + ret0, _ := ret[0].(cache.SharedIndexInformer) + return ret0 +} + +// InformerFor indicates an expected call of InformerFor. +func (mr *MockSharedInformerFactoryMockRecorder) InformerFor(obj, newFunc interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InformerFor", reflect.TypeOf((*MockSharedInformerFactory)(nil).InformerFor), obj, newFunc) +} + +// Shutdown mocks base method. +func (m *MockSharedInformerFactory) Shutdown() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Shutdown") +} + +// Shutdown indicates an expected call of Shutdown. +func (mr *MockSharedInformerFactoryMockRecorder) Shutdown() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Shutdown", reflect.TypeOf((*MockSharedInformerFactory)(nil).Shutdown)) +} + +// Start mocks base method. +func (m *MockSharedInformerFactory) Start(stopCh <-chan struct{}) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Start", stopCh) +} + +// Start indicates an expected call of Start. +func (mr *MockSharedInformerFactoryMockRecorder) Start(stopCh interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockSharedInformerFactory)(nil).Start), stopCh) +} + +// WaitForCacheSync mocks base method. +func (m *MockSharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WaitForCacheSync", stopCh) + ret0, _ := ret[0].(map[reflect.Type]bool) + return ret0 +} + +// WaitForCacheSync indicates an expected call of WaitForCacheSync. +func (mr *MockSharedInformerFactoryMockRecorder) WaitForCacheSync(stopCh interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForCacheSync", reflect.TypeOf((*MockSharedInformerFactory)(nil).WaitForCacheSync), stopCh) +} diff --git a/operator/generated/informers/externalversions/mock/generic.go b/operator/generated/informers/externalversions/mock/generic.go new file mode 100644 index 000000000..11ed83cc5 --- /dev/null +++ b/operator/generated/informers/externalversions/mock/generic.go @@ -0,0 +1,80 @@ +// /* +// Copyright 2023 The KEDA Authors. +// +// Licensed 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. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: operator/generated/informers/externalversions/generic.go + +// Package mock is a generated GoMock package. +package mock + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + cache "k8s.io/client-go/tools/cache" +) + +// MockGenericInformer is a mock of GenericInformer interface. +type MockGenericInformer struct { + ctrl *gomock.Controller + recorder *MockGenericInformerMockRecorder +} + +// MockGenericInformerMockRecorder is the mock recorder for MockGenericInformer. +type MockGenericInformerMockRecorder struct { + mock *MockGenericInformer +} + +// NewMockGenericInformer creates a new mock instance. +func NewMockGenericInformer(ctrl *gomock.Controller) *MockGenericInformer { + mock := &MockGenericInformer{ctrl: ctrl} + mock.recorder = &MockGenericInformerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockGenericInformer) EXPECT() *MockGenericInformerMockRecorder { + return m.recorder +} + +// Informer mocks base method. +func (m *MockGenericInformer) Informer() cache.SharedIndexInformer { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Informer") + ret0, _ := ret[0].(cache.SharedIndexInformer) + return ret0 +} + +// Informer indicates an expected call of Informer. +func (mr *MockGenericInformerMockRecorder) Informer() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Informer", reflect.TypeOf((*MockGenericInformer)(nil).Informer)) +} + +// Lister mocks base method. +func (m *MockGenericInformer) Lister() cache.GenericLister { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Lister") + ret0, _ := ret[0].(cache.GenericLister) + return ret0 +} + +// Lister indicates an expected call of Lister. +func (mr *MockGenericInformerMockRecorder) Lister() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lister", reflect.TypeOf((*MockGenericInformer)(nil).Lister)) +} diff --git a/operator/generated/listers/http/v1alpha1/mock/expansion_generated.go b/operator/generated/listers/http/v1alpha1/mock/expansion_generated.go new file mode 100644 index 000000000..2b3357a03 --- /dev/null +++ b/operator/generated/listers/http/v1alpha1/mock/expansion_generated.go @@ -0,0 +1,72 @@ +// /* +// Copyright 2023 The KEDA Authors. +// +// Licensed 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. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: operator/generated/listers/http/v1alpha1/expansion_generated.go + +// Package mock is a generated GoMock package. +package mock + +import ( + gomock "github.com/golang/mock/gomock" +) + +// MockHTTPScaledObjectListerExpansion is a mock of HTTPScaledObjectListerExpansion interface. +type MockHTTPScaledObjectListerExpansion struct { + ctrl *gomock.Controller + recorder *MockHTTPScaledObjectListerExpansionMockRecorder +} + +// MockHTTPScaledObjectListerExpansionMockRecorder is the mock recorder for MockHTTPScaledObjectListerExpansion. +type MockHTTPScaledObjectListerExpansionMockRecorder struct { + mock *MockHTTPScaledObjectListerExpansion +} + +// NewMockHTTPScaledObjectListerExpansion creates a new mock instance. +func NewMockHTTPScaledObjectListerExpansion(ctrl *gomock.Controller) *MockHTTPScaledObjectListerExpansion { + mock := &MockHTTPScaledObjectListerExpansion{ctrl: ctrl} + mock.recorder = &MockHTTPScaledObjectListerExpansionMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHTTPScaledObjectListerExpansion) EXPECT() *MockHTTPScaledObjectListerExpansionMockRecorder { + return m.recorder +} + +// MockHTTPScaledObjectNamespaceListerExpansion is a mock of HTTPScaledObjectNamespaceListerExpansion interface. +type MockHTTPScaledObjectNamespaceListerExpansion struct { + ctrl *gomock.Controller + recorder *MockHTTPScaledObjectNamespaceListerExpansionMockRecorder +} + +// MockHTTPScaledObjectNamespaceListerExpansionMockRecorder is the mock recorder for MockHTTPScaledObjectNamespaceListerExpansion. +type MockHTTPScaledObjectNamespaceListerExpansionMockRecorder struct { + mock *MockHTTPScaledObjectNamespaceListerExpansion +} + +// NewMockHTTPScaledObjectNamespaceListerExpansion creates a new mock instance. +func NewMockHTTPScaledObjectNamespaceListerExpansion(ctrl *gomock.Controller) *MockHTTPScaledObjectNamespaceListerExpansion { + mock := &MockHTTPScaledObjectNamespaceListerExpansion{ctrl: ctrl} + mock.recorder = &MockHTTPScaledObjectNamespaceListerExpansionMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHTTPScaledObjectNamespaceListerExpansion) EXPECT() *MockHTTPScaledObjectNamespaceListerExpansionMockRecorder { + return m.recorder +} diff --git a/operator/generated/listers/http/v1alpha1/mock/httpscaledobject.go b/operator/generated/listers/http/v1alpha1/mock/httpscaledobject.go new file mode 100644 index 000000000..4f4b7451b --- /dev/null +++ b/operator/generated/listers/http/v1alpha1/mock/httpscaledobject.go @@ -0,0 +1,136 @@ +// /* +// Copyright 2023 The KEDA Authors. +// +// Licensed 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. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: operator/generated/listers/http/v1alpha1/httpscaledobject.go + +// Package mock is a generated GoMock package. +package mock + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + v1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1" + v1alpha10 "github.com/kedacore/http-add-on/operator/generated/listers/http/v1alpha1" + labels "k8s.io/apimachinery/pkg/labels" +) + +// MockHTTPScaledObjectLister is a mock of HTTPScaledObjectLister interface. +type MockHTTPScaledObjectLister struct { + ctrl *gomock.Controller + recorder *MockHTTPScaledObjectListerMockRecorder +} + +// MockHTTPScaledObjectListerMockRecorder is the mock recorder for MockHTTPScaledObjectLister. +type MockHTTPScaledObjectListerMockRecorder struct { + mock *MockHTTPScaledObjectLister +} + +// NewMockHTTPScaledObjectLister creates a new mock instance. +func NewMockHTTPScaledObjectLister(ctrl *gomock.Controller) *MockHTTPScaledObjectLister { + mock := &MockHTTPScaledObjectLister{ctrl: ctrl} + mock.recorder = &MockHTTPScaledObjectListerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHTTPScaledObjectLister) EXPECT() *MockHTTPScaledObjectListerMockRecorder { + return m.recorder +} + +// HTTPScaledObjects mocks base method. +func (m *MockHTTPScaledObjectLister) HTTPScaledObjects(namespace string) v1alpha10.HTTPScaledObjectNamespaceLister { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HTTPScaledObjects", namespace) + ret0, _ := ret[0].(v1alpha10.HTTPScaledObjectNamespaceLister) + return ret0 +} + +// HTTPScaledObjects indicates an expected call of HTTPScaledObjects. +func (mr *MockHTTPScaledObjectListerMockRecorder) HTTPScaledObjects(namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HTTPScaledObjects", reflect.TypeOf((*MockHTTPScaledObjectLister)(nil).HTTPScaledObjects), namespace) +} + +// List mocks base method. +func (m *MockHTTPScaledObjectLister) List(selector labels.Selector) ([]*v1alpha1.HTTPScaledObject, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "List", selector) + ret0, _ := ret[0].([]*v1alpha1.HTTPScaledObject) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockHTTPScaledObjectListerMockRecorder) List(selector interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockHTTPScaledObjectLister)(nil).List), selector) +} + +// MockHTTPScaledObjectNamespaceLister is a mock of HTTPScaledObjectNamespaceLister interface. +type MockHTTPScaledObjectNamespaceLister struct { + ctrl *gomock.Controller + recorder *MockHTTPScaledObjectNamespaceListerMockRecorder +} + +// MockHTTPScaledObjectNamespaceListerMockRecorder is the mock recorder for MockHTTPScaledObjectNamespaceLister. +type MockHTTPScaledObjectNamespaceListerMockRecorder struct { + mock *MockHTTPScaledObjectNamespaceLister +} + +// NewMockHTTPScaledObjectNamespaceLister creates a new mock instance. +func NewMockHTTPScaledObjectNamespaceLister(ctrl *gomock.Controller) *MockHTTPScaledObjectNamespaceLister { + mock := &MockHTTPScaledObjectNamespaceLister{ctrl: ctrl} + mock.recorder = &MockHTTPScaledObjectNamespaceListerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHTTPScaledObjectNamespaceLister) EXPECT() *MockHTTPScaledObjectNamespaceListerMockRecorder { + return m.recorder +} + +// Get mocks base method. +func (m *MockHTTPScaledObjectNamespaceLister) Get(name string) (*v1alpha1.HTTPScaledObject, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", name) + ret0, _ := ret[0].(*v1alpha1.HTTPScaledObject) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockHTTPScaledObjectNamespaceListerMockRecorder) Get(name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockHTTPScaledObjectNamespaceLister)(nil).Get), name) +} + +// List mocks base method. +func (m *MockHTTPScaledObjectNamespaceLister) List(selector labels.Selector) ([]*v1alpha1.HTTPScaledObject, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "List", selector) + ret0, _ := ret[0].([]*v1alpha1.HTTPScaledObject) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockHTTPScaledObjectNamespaceListerMockRecorder) List(selector interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockHTTPScaledObjectNamespaceLister)(nil).List), selector) +} diff --git a/operator/main.go b/operator/main.go index 25afb84b4..f71b5b141 100644 --- a/operator/main.go +++ b/operator/main.go @@ -25,9 +25,7 @@ import ( "github.com/go-logr/logr" kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1" - "golang.org/x/sync/errgroup" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" @@ -40,10 +38,7 @@ import ( httpv1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1" httpcontrollers "github.com/kedacore/http-add-on/operator/controllers/http" "github.com/kedacore/http-add-on/operator/controllers/http/config" - "github.com/kedacore/http-add-on/pkg/build" kedahttp "github.com/kedacore/http-add-on/pkg/http" - "github.com/kedacore/http-add-on/pkg/k8s" - "github.com/kedacore/http-add-on/pkg/routing" // +kubebuilder:scaffold:imports ) @@ -131,7 +126,6 @@ func main() { os.Exit(1) } - routingTable := routing.NewTable() if err = (&httpcontrollers.HTTPScaledObjectReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), @@ -139,7 +133,6 @@ func main() { InterceptorConfig: *interceptorCfg, ExternalScalerConfig: *externalScalerCfg, BaseConfig: *baseConfig, - RoutingTable: routingTable, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "HTTPScaledObject") os.Exit(1) @@ -155,65 +148,16 @@ func main() { os.Exit(1) } - // TODO(pedrotorres): uncomment after implementing new routing table - // setupLog.Info("starting manager") - // if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { - // setupLog.Error(err, "problem running manager") - // os.Exit(1) - // } - - // TODO(pedrotorres): remove everything beyond this line after implementing - // new routing table - ctx := ctrl.SetupSignalHandler() - if err := ensureConfigMap( - ctx, - setupLog, - baseConfig.CurrentNamespace, - routing.ConfigMapRoutingTableName, - ); err != nil { - setupLog.Error( - err, - "unable to find routing table ConfigMap", - "namespace", - baseConfig.CurrentNamespace, - "name", - routing.ConfigMapRoutingTableName, - ) + setupLog.Info("starting manager") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem running manager") os.Exit(1) } - - errGrp, ctx := errgroup.WithContext(ctx) - ctx, done := context.WithCancel(ctx) - - // start the control loop - errGrp.Go(func() error { - defer done() - setupLog.Info("starting manager") - return mgr.Start(ctx) - }) - - // start the admin server to serve routing table information - // to the interceptors - errGrp.Go(func() error { - defer done() - return runAdminServer( - ctx, - ctrl.Log, - routingTable, - adminPort, - baseConfig, - interceptorCfg, - externalScalerCfg, - ) - }) - build.PrintComponentInfo(setupLog, "Operator") - setupLog.Error(errGrp.Wait(), "running the operator") } func runAdminServer( ctx context.Context, lggr logr.Logger, - routingTable *routing.Table, port int, baseCfg *config.Base, interceptorCfg *config.Interceptor, @@ -221,7 +165,6 @@ func runAdminServer( ) error { mux := http.NewServeMux() - routing.AddFetchRoute(setupLog, mux, routingTable) kedahttp.AddConfigEndpoint( lggr.WithName("operatorAdmin"), mux, @@ -238,51 +181,3 @@ func runAdminServer( ) return kedahttp.ServeContext(ctx, addr, mux) } - -// ensureConfigMap returns a non-nil error if the config -// map in the given namespace with the given name -// does not exist, or there was an error finding it. -// -// it returns a nil error if it could be fetched. -// this function works with its own Kubernetes client and -// is intended for use on operator startup, and should -// not be used with the controller library's client, -// since that is not usable until after the controller -// has started up. -func ensureConfigMap( - ctx context.Context, - lggr logr.Logger, - ns, - name string, -) error { - // we need to get our own Kubernetes clientset - // here, rather than using the client.Client from - // the manager because the client will not - // be instantiated by the time we call this. - // You need to start the manager before that client - // is usable. - clset, _, err := k8s.NewClientset() - if err != nil { - lggr.Error( - err, - "couldn't get new clientset", - ) - return err - } - if _, err := clset.CoreV1().ConfigMaps(ns).Get( - ctx, - name, - metav1.GetOptions{}, - ); err != nil { - lggr.Error( - err, - "couldn't find config map", - "namespace", - ns, - "name", - name, - ) - return err - } - return nil -} diff --git a/operator/main_test.go b/operator/main_test.go index a42734c1a..7f315c6d5 100644 --- a/operator/main_test.go +++ b/operator/main_test.go @@ -15,7 +15,6 @@ import ( "k8s.io/apimachinery/pkg/util/rand" "github.com/kedacore/http-add-on/operator/controllers/http/config" - "github.com/kedacore/http-add-on/pkg/routing" "github.com/kedacore/http-add-on/pkg/test" ) @@ -36,7 +35,6 @@ func TestRunAdminServerConfig(t *testing.T) { return runAdminServer( ctx, lggr, - routing.NewTable(), port, baseCfg, interceptorCfg, diff --git a/pkg/k8s/client.go b/pkg/k8s/client.go deleted file mode 100644 index 13c066ad0..000000000 --- a/pkg/k8s/client.go +++ /dev/null @@ -1,36 +0,0 @@ -package k8s - -import ( - "github.com/pkg/errors" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -// NewClientset gets a new Kubernetes clientset, or calls log.Fatal -// if it couldn't -func NewClientset() (*kubernetes.Clientset, dynamic.Interface, error) { - // creates the in-cluster config - config, err := rest.InClusterConfig() - if err != nil { - return nil, nil, errors.Wrap(err, "Getting in-cluster config") - } - // creates the clientset - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, nil, errors.Wrap(err, "Creating k8s clientset") - } - dynamic, err := dynamic.NewForConfig(config) - - if err != nil { - return nil, nil, err - } - return clientset, dynamic, nil -} - -// ObjKey creates a new client.ObjectKey with the given -// name and namespace -func ObjKey(ns, name string) client.ObjectKey { - return client.ObjectKey{Namespace: ns, Name: name} -} diff --git a/pkg/k8s/client_fake.go b/pkg/k8s/client_fake.go deleted file mode 100644 index 6115ddd8b..000000000 --- a/pkg/k8s/client_fake.go +++ /dev/null @@ -1,177 +0,0 @@ -package k8s - -import ( - "context" - "encoding/json" - - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -var _ client.Client = &FakeRuntimeClient{} -var _ client.Reader = &FakeRuntimeClientReader{} -var _ client.Writer = &FakeRuntimeClientWriter{} -var _ client.StatusClient = &FakeRuntimeStatusClient{} - -// FakeRuntimeClient is a fake implementation of -// (k8s.io/controller-runtime/pkg/client).Client -type FakeRuntimeClient struct { - *FakeRuntimeClientReader - *FakeRuntimeClientWriter - *FakeRuntimeStatusClient -} - -func NewFakeRuntimeClient() *FakeRuntimeClient { - return &FakeRuntimeClient{ - FakeRuntimeClientReader: &FakeRuntimeClientReader{}, - FakeRuntimeClientWriter: &FakeRuntimeClientWriter{}, - FakeRuntimeStatusClient: &FakeRuntimeStatusClient{}, - } -} - -// Scheme implements the controller-runtime Client interface. -// -// NOTE: this method is not implemented and always returns nil. -func (f *FakeRuntimeStatusClient) Scheme() *runtime.Scheme { - return nil -} - -// RESTMapper implements the controller-runtime Client interface. -// -// NOTE: this method is not implemented and always returns nil. -func (f *FakeRuntimeClientReader) RESTMapper() meta.RESTMapper { - return nil -} - -type GetCall struct { - Key client.ObjectKey - Obj client.Object -} - -// FakeRuntimeClientReader is a fake implementation of -// (k8s.io/controller-runtime/pkg/client).ClientReader -type FakeRuntimeClientReader struct { - GetCalls []GetCall - GetFunc func() client.Object - ListCalls []client.ObjectList - ListFunc func() client.ObjectList -} - -func (f *FakeRuntimeClientReader) Get( - ctx context.Context, - key client.ObjectKey, - obj client.Object, - opts ...client.GetOption, -) error { - f.GetCalls = append(f.GetCalls, GetCall{ - Key: key, - Obj: obj, - }) - // marshal the GetFunc return value, then unmarshal - // it back into the obj parameter. - b, err := json.Marshal(f.GetFunc()) - if err != nil { - return err - } - if err := json.Unmarshal(b, obj); err != nil { - return err - } - - return nil -} - -func (f *FakeRuntimeClientReader) List( - ctx context.Context, - list client.ObjectList, - opts ...client.ListOption, -) error { - f.ListCalls = append(f.ListCalls, list) - b, err := json.Marshal(f.ListFunc()) - if err != nil { - return err - } - if err := json.Unmarshal(b, list); err != nil { - return err - } - return nil -} - -// FakeRuntimeClientWriter is a fake implementation of -// (k8s.io/controller-runtime/pkg/client).ClientWriter -// -// It stores all method calls in the respective struct -// fields. Instances of FakeRuntimeClientWriter are not -// concurrency-safe -type FakeRuntimeClientWriter struct { - Creates []client.Object - Deletes []client.Object - Updates []client.Object - Patches []client.Object - DeleteAllOfs []client.Object -} - -func (f *FakeRuntimeClientWriter) Create( - ctx context.Context, - obj client.Object, - opts ...client.CreateOption, -) error { - f.Creates = append(f.Creates, obj) - return nil -} - -func (f *FakeRuntimeClientWriter) Delete( - ctx context.Context, - obj client.Object, - opts ...client.DeleteOption, -) error { - f.Deletes = append(f.Deletes, obj) - return nil -} - -func (f *FakeRuntimeClientWriter) Update( - ctx context.Context, - obj client.Object, - opts ...client.UpdateOption, -) error { - f.Updates = append(f.Updates, obj) - return nil -} - -func (f *FakeRuntimeClientWriter) Patch( - ctx context.Context, - obj client.Object, - patch client.Patch, - opts ...client.PatchOption, -) error { - f.Patches = append(f.Patches, obj) - return nil -} - -func (f *FakeRuntimeClientWriter) DeleteAllOf( - ctx context.Context, - obj client.Object, - opts ...client.DeleteAllOfOption, -) error { - f.DeleteAllOfs = append(f.DeleteAllOfs, obj) - return nil -} - -// FakeRuntimeStatusClient is a fake implementation of -// (k8s.io/controller-runtime/pkg/client).StatusClient -type FakeRuntimeStatusClient struct { -} - -// Status implements the controller-runtime StatusClient -// interface. -// -// NOTE: this function isn't implemented and always returns -// nil. -func (f *FakeRuntimeStatusClient) Status() client.StatusWriter { - return nil -} - -// SubResource implements client.Client -func (*FakeRuntimeClient) SubResource(subResource string) client.SubResourceClient { - panic("unimplemented") -} diff --git a/pkg/k8s/config_map.go b/pkg/k8s/config_map.go deleted file mode 100644 index 9335f4318..000000000 --- a/pkg/k8s/config_map.go +++ /dev/null @@ -1,79 +0,0 @@ -package k8s - -import ( - "context" - - "github.com/go-logr/logr" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/watch" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -// ConfigMapGetter is a pared down version of a ConfigMapInterface -// (found here: https://pkg.go.dev/k8s.io/client-go@v0.21.3/kubernetes/typed/core/v1#ConfigMapInterface). -// -// Pass this whenever possible to functions that only need to get individual ConfigMaps -// from Kubernetes, and nothing else. -type ConfigMapGetter interface { - Get(ctx context.Context, name string, opts metav1.GetOptions) (*corev1.ConfigMap, error) -} - -// ConfigMapWatcher is a pared down version of a ConfigMapInterface -// (found here: https://pkg.go.dev/k8s.io/client-go@v0.21.3/kubernetes/typed/core/v1#ConfigMapInterface). -// -// Pass this whenever possible to functions that only need to watch for ConfigMaps -// from Kubernetes, and nothing else. -type ConfigMapWatcher interface { - Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) -} - -// ConfigMapGetterWatcher is a pared down version of a ConfigMapInterface -// (found here: https://pkg.go.dev/k8s.io/client-go@v0.21.3/kubernetes/typed/core/v1#ConfigMapInterface). -// -// Pass this whenever possible to functions that only need to watch for ConfigMaps -// from Kubernetes, and nothing else. -type ConfigMapGetterWatcher interface { - ConfigMapGetter - ConfigMapWatcher -} - -func PatchConfigMap( - ctx context.Context, - logger logr.Logger, - cl client.Writer, - originalConfigMap *corev1.ConfigMap, - patchConfigMap *corev1.ConfigMap, -) (*corev1.ConfigMap, error) { - logger = logger.WithName("pkg.k8s.PatchConfigMap") - if err := cl.Patch( - ctx, - patchConfigMap, - client.MergeFrom(originalConfigMap), - ); err != nil { - logger.Error( - err, - "failed to patch ConfigMap", - "originalConfigMap", - *originalConfigMap, - "patchConfigMap", - *patchConfigMap, - ) - return nil, err - } - return patchConfigMap, nil -} - -func GetConfigMap( - ctx context.Context, - cl client.Client, - namespace string, - name string, -) (*corev1.ConfigMap, error) { - configMap := &corev1.ConfigMap{} - err := cl.Get(ctx, client.ObjectKey{Name: name, Namespace: namespace}, configMap) - if err != nil { - return nil, err - } - return configMap, nil -} diff --git a/pkg/k8s/config_map_cache_informer.go b/pkg/k8s/config_map_cache_informer.go deleted file mode 100644 index fd2bf6b71..000000000 --- a/pkg/k8s/config_map_cache_informer.go +++ /dev/null @@ -1,150 +0,0 @@ -package k8s - -import ( - "context" - "encoding/json" - "fmt" - "time" - - "github.com/go-logr/logr" - "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/informers" - infcorev1 "k8s.io/client-go/informers/core/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/cache" -) - -type InformerConfigMapUpdater struct { - lggr logr.Logger - cmInformer infcorev1.ConfigMapInformer - bcaster *watch.Broadcaster -} - -func (i *InformerConfigMapUpdater) MarshalJSON() ([]byte, error) { - lst := i.cmInformer.Lister() - cms, err := lst.List(labels.Everything()) - if err != nil { - return nil, err - } - return json.Marshal(&cms) -} - -func (i *InformerConfigMapUpdater) Start(ctx context.Context) error { - i.cmInformer.Informer().Run(ctx.Done()) - return errors.Wrap( - ctx.Err(), - "configMap informer was stopped", - ) -} - -func (i *InformerConfigMapUpdater) Get( - ns, - name string, -) (corev1.ConfigMap, error) { - cm, err := i.cmInformer.Lister().ConfigMaps(ns).Get(name) - if err != nil { - return corev1.ConfigMap{}, err - } - return *cm, nil -} - -func (i *InformerConfigMapUpdater) Watch( - ns, - name string, -) (watch.Interface, error) { - watched, err := i.bcaster.Watch() - if err != nil { - return nil, err - } - return watch.Filter(watched, func(e watch.Event) (watch.Event, bool) { - cm, ok := e.Object.(*corev1.ConfigMap) - if !ok { - i.lggr.Error( - fmt.Errorf("informer expected ConfigMap, ignoring this event"), - "event", - e, - ) - return e, false - } - if cm.Namespace == ns && cm.Name == name { - return e, true - } - return e, false - }), nil -} - -func (i *InformerConfigMapUpdater) addEvtHandler(obj interface{}) { - cm, ok := obj.(*corev1.ConfigMap) - if !ok { - i.lggr.Error( - fmt.Errorf("informer expected configMap, got %v", obj), - "not forwarding event", - ) - return - } - - if err := i.bcaster.Action(watch.Added, cm); err != nil { - i.lggr.Error(err, "informer expected configMap") - } -} - -func (i *InformerConfigMapUpdater) updateEvtHandler(oldObj, newObj interface{}) { - cm, ok := newObj.(*corev1.ConfigMap) - if !ok { - i.lggr.Error( - fmt.Errorf("informer expected configMap, got %v", newObj), - "not forwarding event", - ) - return - } - - if err := i.bcaster.Action(watch.Modified, cm); err != nil { - i.lggr.Error(err, "informer expected configMap") - } -} - -func (i *InformerConfigMapUpdater) deleteEvtHandler(obj interface{}) { - cm, ok := obj.(*corev1.ConfigMap) - if !ok { - i.lggr.Error( - fmt.Errorf("informer expected configMap, got %v", obj), - "not forwarding event", - ) - return - } - - if err := i.bcaster.Action(watch.Deleted, cm); err != nil { - i.lggr.Error(err, "informer expected configMap") - } -} - -func NewInformerConfigMapUpdater( - lggr logr.Logger, - cl kubernetes.Interface, - defaultResync time.Duration, - namespace string, -) *InformerConfigMapUpdater { - factory := informers.NewSharedInformerFactoryWithOptions( - cl, - defaultResync, - informers.WithNamespace(namespace), - ) - cmInformer := factory.Core().V1().ConfigMaps() - ret := &InformerConfigMapUpdater{ - lggr: lggr, - bcaster: watch.NewBroadcaster(0, watch.WaitIfChannelFull), - cmInformer: cmInformer, - } - _, err := ret.cmInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: ret.addEvtHandler, - UpdateFunc: ret.updateEvtHandler, - DeleteFunc: ret.deleteEvtHandler, - }) - if err != nil { - lggr.Error(err, "error creating config informer") - } - return ret -} diff --git a/pkg/k8s/config_map_fake.go b/pkg/k8s/config_map_fake.go deleted file mode 100644 index 1e3c77671..000000000 --- a/pkg/k8s/config_map_fake.go +++ /dev/null @@ -1,20 +0,0 @@ -package k8s - -import ( - "context" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type FakeConfigMapGetter struct { - ConfigMap *corev1.ConfigMap - Err error -} - -func (f FakeConfigMapGetter) Get(ctx context.Context, name string, opts metav1.GetOptions) (*corev1.ConfigMap, error) { - if f.Err != nil { - return nil, f.Err - } - return f.ConfigMap, nil -} diff --git a/pkg/k8s/deployment_cache_informer.go b/pkg/k8s/deployment_cache_informer.go index 38ee17b84..80ca6b5cc 100644 --- a/pkg/k8s/deployment_cache_informer.go +++ b/pkg/k8s/deployment_cache_informer.go @@ -82,7 +82,7 @@ func (i *InformerBackedDeploymentCache) addEvtHandler(obj interface{}) { } } -func (i *InformerBackedDeploymentCache) updateEvtHandler(oldObj, newObj interface{}) { +func (i *InformerBackedDeploymentCache) updateEvtHandler(_, newObj interface{}) { depl, ok := newObj.(*appsv1.Deployment) if !ok { i.lggr.Error( diff --git a/pkg/queue/queue_rpc.go b/pkg/queue/queue_rpc.go index 542ebbdc3..4ff3ed9b6 100644 --- a/pkg/queue/queue_rpc.go +++ b/pkg/queue/queue_rpc.go @@ -1,7 +1,6 @@ package queue import ( - "context" "encoding/json" "fmt" "net/http" @@ -61,8 +60,6 @@ func newSizeHandler( // from the given hostAndPort. Note that the hostAndPort should // not end with a "/" and shouldn't include a path. func GetCounts( - ctx context.Context, - lggr logr.Logger, httpCl *http.Client, interceptorURL url.URL, ) (*Counts, error) { diff --git a/pkg/queue/queue_rpc_test.go b/pkg/queue/queue_rpc_test.go index 48887e6e4..e865ba612 100644 --- a/pkg/queue/queue_rpc_test.go +++ b/pkg/queue/queue_rpc_test.go @@ -1,7 +1,6 @@ package queue import ( - "context" "encoding/json" "errors" "testing" @@ -54,7 +53,6 @@ func TestQueueSizeHandlerFail(t *testing.T) { } func TestQueueSizeHandlerIntegration(t *testing.T) { - ctx := context.Background() lggr := logr.Discard() r := require.New(t) reader := &FakeCountReader{ @@ -67,7 +65,7 @@ func TestQueueSizeHandlerIntegration(t *testing.T) { r.NoError(err) defer srv.Close() httpCl := srv.Client() - counts, err := GetCounts(ctx, lggr, httpCl, *url) + counts, err := GetCounts(httpCl, *url) r.NoError(err) r.Equal(1, len(counts.Counts)) for _, val := range counts.Counts { diff --git a/pkg/routing/atomicvalue.go b/pkg/routing/atomicvalue.go new file mode 100644 index 000000000..dc6c9b23d --- /dev/null +++ b/pkg/routing/atomicvalue.go @@ -0,0 +1,28 @@ +package routing + +import ( + "sync/atomic" +) + +type AtomicValue[V any] struct { + atomicValue atomic.Value +} + +func NewAtomicValue[V any](v V) *AtomicValue[V] { + var av AtomicValue[V] + av.Set(v) + + return &av +} + +func (av *AtomicValue[V]) Get() V { + if v, ok := av.atomicValue.Load().(V); ok { + return v + } + + return *new(V) +} + +func (av *AtomicValue[V]) Set(v V) { + av.atomicValue.Store(v) +} diff --git a/pkg/routing/config_map.go b/pkg/routing/config_map.go deleted file mode 100644 index bb2660843..000000000 --- a/pkg/routing/config_map.go +++ /dev/null @@ -1,117 +0,0 @@ -package routing - -import ( - "context" - "fmt" - - "github.com/go-logr/logr" - "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/kedacore/http-add-on/pkg/k8s" - "github.com/kedacore/http-add-on/pkg/queue" -) - -const ( - // the name of the ConfigMap that stores the routing table - ConfigMapRoutingTableName = "keda-http-add-on-routing-table" - // the key in the ConfigMap data that stores the JSON routing table - configMapRoutingTableKey = "routing-table" -) - -// SaveTableToConfigMap saves the contents of table to the Data field in -// configMap -func SaveTableToConfigMap(table *Table, configMap *corev1.ConfigMap) error { - tableAsJSON, err := table.MarshalJSON() - if err != nil { - return err - } - configMap.Data[configMapRoutingTableKey] = string(tableAsJSON) - return nil -} - -// FetchTableFromConfigMap fetches the Data field from configMap, converts it -// to a routing table, and returns it -func FetchTableFromConfigMap(configMap *corev1.ConfigMap) (*Table, error) { - data, found := configMap.Data[configMapRoutingTableKey] - if !found { - return nil, fmt.Errorf( - "no '%s' key found in the %s ConfigMap", - configMapRoutingTableKey, - ConfigMapRoutingTableName, - ) - } - ret := NewTable() - if err := ret.UnmarshalJSON([]byte(data)); err != nil { - retErr := errors.Wrap( - err, - fmt.Sprintf( - "error decoding '%s' key in %s ConfigMap", - configMapRoutingTableKey, - ConfigMapRoutingTableName, - ), - ) - return nil, retErr - } - return ret, nil -} - -// GetTable fetches the contents of the appropriate ConfigMap that stores -// the routing table, then tries to decode it into a temporary routing table -// data structure. -// -// If that succeeds, it calls table.Replace(newTable), then ensures that -// every host in the routing table exists in the given queue, and no hosts -// exist in the queue that don't exist in the routing table. It uses q.Ensure() -// and q.Remove() to do those things, respectively. -func GetTable( - ctx context.Context, - lggr logr.Logger, - getter k8s.ConfigMapGetter, - table *Table, - q queue.Counter, -) error { - lggr = lggr.WithName("pkg.routing.GetTable") - - cm, err := getter.Get( - ctx, - ConfigMapRoutingTableName, - metav1.GetOptions{}, - ) - if err != nil { - lggr.Error( - err, - "failed to fetch routing table config map", - "configMapName", - ConfigMapRoutingTableName, - ) - return errors.Wrap( - err, - fmt.Sprintf( - "failed to fetch ConfigMap %s", - ConfigMapRoutingTableName, - ), - ) - } - newTable, err := FetchTableFromConfigMap(cm) - if err != nil { - lggr.Error( - err, - "failed decoding routing table ConfigMap", - "configMapName", - ConfigMapRoutingTableName, - ) - return errors.Wrap( - err, - fmt.Sprintf( - "failed decoding ConfigMap %s into a routing table", - ConfigMapRoutingTableName, - ), - ) - } - - table.Replace(newTable) - - return nil -} diff --git a/pkg/routing/config_map_updater.go b/pkg/routing/config_map_updater.go deleted file mode 100644 index 50a5ac65b..000000000 --- a/pkg/routing/config_map_updater.go +++ /dev/null @@ -1,88 +0,0 @@ -package routing - -import ( - "context" - - "github.com/go-logr/logr" - "github.com/pkg/errors" - "golang.org/x/sync/errgroup" - corev1 "k8s.io/api/core/v1" - - "github.com/kedacore/http-add-on/pkg/k8s" -) - -// StartConfigMapRoutingTableUpdater starts a loop that does the following: -// -// - Fetches a full version of the ConfigMap called ConfigMapRoutingTableName in -// the given namespace ns, and calls table.Replace(newTable) after it does so -// - Uses watcher to watch for all ADDED or CREATED events on the ConfigMap -// called ConfigMapRoutingTableName. On either of those events, decodes -// that ConfigMap into a routing table and stores the new table into table -// using table.Replace(newTable) -// - Execute the callback function, if one exists -// - Returns an appropriate non-nil error if ctx.Done() receives -func StartConfigMapRoutingTableUpdater( - ctx context.Context, - lggr logr.Logger, - cmInformer *k8s.InformerConfigMapUpdater, - ns string, - table *Table, - cbFunc func() error, -) error { - lggr = lggr.WithName("pkg.routing.StartConfigMapRoutingTableUpdater") - - watcher, err := cmInformer.Watch(ns, ConfigMapRoutingTableName) - if err != nil { - return err - } - defer watcher.Stop() - - ctx, done := context.WithCancel(ctx) - defer done() - grp, ctx := errgroup.WithContext(ctx) - - grp.Go(func() error { - defer done() - return cmInformer.Start(ctx) - }) - - grp.Go(func() error { - defer done() - for { - select { - case event := <-watcher.ResultChan(): - cm, ok := event.Object.(*corev1.ConfigMap) - // Theoretically this will not happen - if !ok { - lggr.Info( - "The event object observed is not a configmap", - ) - continue - } - newTable, err := FetchTableFromConfigMap(cm) - if err != nil { - return err - } - table.Replace(newTable) - // Execute the callback function, if one exists - if cbFunc != nil { - if err := cbFunc(); err != nil { - lggr.Error( - err, - "failed to exec the callback function", - ) - continue - } - } - case <-ctx.Done(): - return errors.Wrap(ctx.Err(), "context is done") - } - } - }) - - if err := grp.Wait(); err != nil { - lggr.Error(err, "config map routing updater is failed") - return err - } - return nil -} diff --git a/pkg/routing/config_map_updater_test.go b/pkg/routing/config_map_updater_test.go deleted file mode 100644 index 28cf0f63e..000000000 --- a/pkg/routing/config_map_updater_test.go +++ /dev/null @@ -1,205 +0,0 @@ -package routing - -import ( - "context" - "errors" - "strings" - "testing" - "time" - - "github.com/go-logr/logr" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "golang.org/x/sync/errgroup" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes/fake" - clgotesting "k8s.io/client-go/testing" - - "github.com/kedacore/http-add-on/pkg/k8s" - "github.com/kedacore/http-add-on/pkg/queue" -) - -// fake adapters for the k8s.GetterWatcher interface. -// -// Note that there is another way to fake the k8s getter and -// watcher types. -// -// we could use the "fake" package in k8s.io/client-go -// (https://pkg.go.dev/k8s.io/client-go@v0.22.0/kubernetes/fake) -// instead of creating and using these structs, but doing so -// requires internal knowledge of several layers of the client-go -// module, since it's not well documented (even if it were, -// you would need to touch a few different packages to get it -// working). -// -// I've (arschles) chosen to create these structs and sidestep -// the entire process, since this approach is explicit and only -// requires knowledge of the k8s.GetterWatcher interface in this -// codebase, the standard k8s/client-go package (which you -// already need to know to understand this codebase), and the -// fake watcher, which you would need to understand using either -// approach. The fake watcher documentation is linked below: -// -// (https://pkg.go.dev/k8s.io/apimachinery@v0.21.3/pkg/watch#NewFake), - -func TestStartUpdateLoop(t *testing.T) { - r := require.New(t) - a := assert.New(t) - lggr := logr.Discard() - ctx, done := context.WithCancel(context.Background()) - // ensure that we call done so that we clean - // up running test resources like the update loop, etc... - defer done() - const ( - interval = 10 * time.Millisecond - ns = "testns" - ) - - q := queue.NewFakeCounter() - table := NewTable() - r.NoError(table.AddTarget("host1", NewTarget( - "testns", - "svc1", - 8080, - "depl1", - 100, - ))) - r.NoError(table.AddTarget("host2", NewTarget( - "testns", - "svc2", - 8080, - "depl2", - 100, - ))) - - cm := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: ConfigMapRoutingTableName, - Namespace: ns, - }, - Data: map[string]string{}, - } - r.NoError(SaveTableToConfigMap(table, cm)) - - fakeGetter := fake.NewSimpleClientset(cm) - - configMapInformer := k8s.NewInformerConfigMapUpdater( - lggr, - fakeGetter, - time.Second*1, - ns, - ) - - grp, ctx := errgroup.WithContext(ctx) - - grp.Go(func() error { - err := StartConfigMapRoutingTableUpdater( - ctx, - lggr, - configMapInformer, - ns, - table, - nil, - ) - // we purposefully cancel the context below, - // so we need to ignore that error. - if !errors.Is(err, context.Canceled) { - return err - } - return nil - }) - - // send a watch event in parallel. we'll ensure that it - // made it through in the below loop - grp.Go(func() error { - if _, err := fakeGetter. - CoreV1(). - ConfigMaps(ns). - Create(ctx, cm, metav1.CreateOptions{}); err != nil && strings.Contains( - err.Error(), - "already exists", - ) { - if err := fakeGetter. - CoreV1(). - ConfigMaps(ns). - Delete(ctx, cm.Name, metav1.DeleteOptions{}); err != nil { - return err - } - if _, err := fakeGetter. - CoreV1(). - ConfigMaps(ns). - Create(ctx, cm, metav1.CreateOptions{}); err != nil { - return err - } - } - return nil - }) - - cmGetActions := []clgotesting.Action{} - otherGetActions := []clgotesting.Action{} - const waitDur = interval * 5 - time.Sleep(waitDur) - - _, err := fakeGetter. - CoreV1(). - ConfigMaps(ns). - Get(ctx, ConfigMapRoutingTableName, metav1.GetOptions{}) - r.NoError(err) - - for _, action := range fakeGetter.Actions() { - verb := action.GetVerb() - resource := action.GetResource().Resource - // record, then ignore all actions that were not for - // ConfigMaps. - // the loop should not do anything with other resources - if resource != "configmaps" { - otherGetActions = append(otherGetActions, action) - continue - } else if verb == "get" { - cmGetActions = append(cmGetActions, action) - } - } - - // assert (don't require) these conditions so that - // we can check them, fail if necessary, but continue onward - // to check the result of the error group afterward - a.Equal( - 0, - len(otherGetActions), - "unexpected actions on non-ConfigMap resources: %s", - otherGetActions, - ) - a.Greater( - len(cmGetActions), - 0, - "no get actions for ConfigMaps", - ) - - done() - // if this test returns without timing out, - // then we can be sure that the fakeWatcher was - // able to send a watch event. if that times out - // or otherwise fails, the update loop was not properly - // listening for these events. - r.NoError(grp.Wait()) - - // the queue won't _necessarily_ have all the hosts that - // the table has in it. Hosts only show up after - // 1 or more requests have been made for it. - // check to make sure that all hosts that are in the - // queue are in the table. - table.l.RLock() - defer table.l.RUnlock() - curTable := table.m - curQCounts, err := q.Current() - r.NoError(err) - for qHost := range curQCounts.Counts { - _, ok := curTable[qHost] - r.True( - ok, - "host %s not found in table", - qHost, - ) - } -} diff --git a/pkg/routing/signaler.go b/pkg/routing/signaler.go new file mode 100644 index 000000000..cf9c5a0da --- /dev/null +++ b/pkg/routing/signaler.go @@ -0,0 +1,34 @@ +package routing + +import ( + "context" +) + +type Signaler interface { + Signal() + Wait(ctx context.Context) error +} + +type signaler chan struct{} + +func NewSignaler() Signaler { + return make(signaler, 1) +} + +var _ Signaler = (*signaler)(nil) + +func (s signaler) Signal() { + select { + case s <- struct{}{}: + default: + } +} + +func (s signaler) Wait(ctx context.Context) error { + select { + case <-s: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} diff --git a/pkg/routing/table.go b/pkg/routing/table.go index 7cffc984b..2d3c6d245 100644 --- a/pkg/routing/table.go +++ b/pkg/routing/table.go @@ -1,135 +1,211 @@ package routing import ( - "bytes" - "encoding/json" - "fmt" - "strings" + "context" + "net/http" "sync" + + "github.com/pkg/errors" + "golang.org/x/sync/errgroup" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/cache" + + httpv1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1" + "github.com/kedacore/http-add-on/operator/generated/informers/externalversions" + informershttpv1alpha1 "github.com/kedacore/http-add-on/operator/generated/informers/externalversions/http/v1alpha1" + listershttpv1alpha1 "github.com/kedacore/http-add-on/operator/generated/listers/http/v1alpha1" ) -type TableReader interface { - Lookup(string) (*Target, error) - Hosts() []string - HasHost(string) bool +type Table interface { + Start(ctx context.Context) error + Route(req *http.Request) *httpv1alpha1.HTTPScaledObject + HasSynced() bool } -type Table struct { - fmt.Stringer - m map[string]Target - l *sync.RWMutex + +type table struct { + // TODO(pedrotorres): remove after upgrading k8s.io/client-go to v0.27.0 + httpScaledObjectLister listershttpv1alpha1.HTTPScaledObjectLister + httpScaledObjectInformer cache.SharedIndexInformer + httpScaledObjectEventHandlerRegistration cache.ResourceEventHandlerRegistration + httpScaledObjects map[types.NamespacedName]*httpv1alpha1.HTTPScaledObject + httpScaledObjectsMutex sync.RWMutex + memoryHolder AtomicValue[TableMemory] + memorySignaler Signaler } -func NewTable() *Table { - return &Table{ - m: make(map[string]Target), - l: new(sync.RWMutex), +func NewTable(sharedInformerFactory externalversions.SharedInformerFactory, namespace string) (Table, error) { + httpScaledObjects := informershttpv1alpha1.New(sharedInformerFactory, namespace, nil).HTTPScaledObjects() + + t := table{ + // TODO(pedrotorres): remove after upgrading k8s.io/client-go to v0.27.0 + httpScaledObjectLister: httpScaledObjects.Lister(), + httpScaledObjectInformer: httpScaledObjects.Informer(), + httpScaledObjects: make(map[types.NamespacedName]*httpv1alpha1.HTTPScaledObject), + memorySignaler: NewSignaler(), } -} -// Hosts is the TableReader implementation for t. -// This function returns all hosts that are currently -// in t. -func (t Table) Hosts() []string { - t.l.RLock() - defer t.l.RUnlock() - ret := make([]string, 0, len(t.m)) - for host := range t.m { - ret = append(ret, host) + registration, err := t.httpScaledObjectInformer.AddEventHandler(&t) + if err != nil { + return nil, err } - return ret + t.httpScaledObjectEventHandlerRegistration = registration + + return &t, nil } -func (t Table) HasHost(host string) bool { - t.l.RLock() - defer t.l.RUnlock() - _, exists := t.m[host] - return exists +// TODO(pedrotorres): remove after upgrading k8s.io/client-go to v0.27.0 +func (t *table) init() error { + httpScaledObjects, err := t.httpScaledObjectLister.List(labels.Everything()) + if err != nil { + return err + } + + for _, httpScaledObject := range httpScaledObjects { + t.OnAdd(httpScaledObject) + } + + return nil } -func (t *Table) String() string { - t.l.RLock() - defer t.l.RUnlock() - return fmt.Sprintf("%v", t.m) +func (t *table) runInformer(ctx context.Context) error { + t.httpScaledObjectInformer.Run(ctx.Done()) + + select { + case <-ctx.Done(): + return ctx.Err() + default: + return errors.New("The sharedIndexInformer has started, run more than once is not allowed") + } } -func (t *Table) MarshalJSON() ([]byte, error) { - t.l.RLock() - defer t.l.RUnlock() - var b bytes.Buffer - err := json.NewEncoder(&b).Encode(t.m) - if err != nil { - return nil, err +func (t *table) refreshMemory(ctx context.Context) error { + // TODO(pedrotorres): uncomment after upgrading k8s.io/client-go to v0.27.0 + // // wait for event handler to be synced before first computation of routes + // for !t.httpScaledObjectEventHandlerRegistration.HasSynced() { + // select { + // case <-ctx.Done(): + // return ctx.Err() + // case <-time.After(time.Second): + // continue + // } + // } + + for { + m := t.newMemoryFromHTTPSOs() + t.memoryHolder.Set(m) + + if err := t.memorySignaler.Wait(ctx); err != nil { + return err + } } - return b.Bytes(), nil } -func (t *Table) UnmarshalJSON(data []byte) error { - t.l.Lock() - defer t.l.Unlock() - t.m = map[string]Target{} - b := bytes.NewBuffer(data) - return json.NewDecoder(b).Decode(&t.m) +func (t *table) newMemoryFromHTTPSOs() TableMemory { + t.httpScaledObjectsMutex.RLock() + defer t.httpScaledObjectsMutex.RUnlock() + + tm := NewTableMemory() + for _, newHTTPSO := range t.httpScaledObjects { + if oldHTTPSO := tm.Recall(newHTTPSO); oldHTTPSO != nil { + // oldest HTTPScaledObject has precedence + if newHTTPSO.CreationTimestamp.After(oldHTTPSO.CreationTimestamp.Time) { + continue + } + } + + tm = tm.Remember(newHTTPSO) + } + + return tm } -func (t *Table) Lookup(host string) (*Target, error) { - t.l.RLock() - defer t.l.RUnlock() +var _ Table = (*table)(nil) - keys := []string{host} - if i := strings.LastIndex(host, ":"); i != -1 { - keys = append(keys, host[:i]) +func (t *table) Start(ctx context.Context) error { + // TODO(pedrotorres): remove after upgrading k8s.io/client-go to v0.27.0 + if err := t.init(); err != nil { + return err } - for _, key := range keys { - if target, ok := t.m[key]; ok { - return &target, nil - } + eg, ctx := errgroup.WithContext(ctx) + eg.Go(applyContext(ctx, t.runInformer)) + eg.Go(applyContext(ctx, t.refreshMemory)) + return eg.Wait() +} + +func (t *table) Route(req *http.Request) *httpv1alpha1.HTTPScaledObject { + if req == nil { + return nil } - return nil, ErrTargetNotFound + url := *req.URL + url.Host = req.Host + + tm := t.memoryHolder.Get() + return tm.Route(&url) } -// AddTarget registers target for host in the routing table t -// if it didn't already exist. -// -// returns a non-nil error if it did already exist -func (t *Table) AddTarget( - host string, - target Target, -) error { - t.l.Lock() - defer t.l.Unlock() - _, ok := t.m[host] - if ok { - return fmt.Errorf( - "host %s is already registered in the routing table", - host, - ) +func (t *table) HasSynced() bool { + tm := t.memoryHolder.Get() + return tm != nil +} + +var _ cache.ResourceEventHandler = (*table)(nil) + +func (t *table) OnAdd(obj interface{}) { + httpScaledObject, ok := obj.(*httpv1alpha1.HTTPScaledObject) + if !ok { + return } - t.m[host] = target - return nil + key := toNamespacedName(httpScaledObject) + + defer t.memorySignaler.Signal() + + t.httpScaledObjectsMutex.Lock() + defer t.httpScaledObjectsMutex.Unlock() + + t.httpScaledObjects[key] = httpScaledObject } -// RemoveTarget removes host, if it exists, and its corresponding Target entry in -// the routing table. If it does not exist, returns a non-nil error -func (t *Table) RemoveTarget(host string) error { - t.l.Lock() - defer t.l.Unlock() - _, ok := t.m[host] +func (t *table) OnUpdate(oldObj interface{}, newObj interface{}) { + oldHTTPSO, ok := oldObj.(*httpv1alpha1.HTTPScaledObject) if !ok { - return fmt.Errorf("host %s did not exist in the routing table", host) + return + } + oldKey := toNamespacedName(oldHTTPSO) + + newHTTPSO, ok := newObj.(*httpv1alpha1.HTTPScaledObject) + if !ok { + return + } + newKey := toNamespacedName(newHTTPSO) + + mustDelete := oldKey != newKey + + defer t.memorySignaler.Signal() + + t.httpScaledObjectsMutex.Lock() + defer t.httpScaledObjectsMutex.Unlock() + + t.httpScaledObjects[newKey] = newHTTPSO + + if mustDelete { + delete(t.httpScaledObjects, oldKey) } - delete(t.m, host) - return nil } -// Replace replaces t's routing table with newTable's. -// -// This function is concurrency safe for t, but not for newTable. -// The caller must ensure that no other goroutine is writing to -// newTable at the time at which they call this function. -func (t *Table) Replace(newTable *Table) { - t.l.Lock() - defer t.l.Unlock() - t.m = newTable.m +func (t *table) OnDelete(obj interface{}) { + httpScaledObject, ok := obj.(*httpv1alpha1.HTTPScaledObject) + if !ok { + return + } + key := toNamespacedName(httpScaledObject) + + defer t.memorySignaler.Signal() + + t.httpScaledObjectsMutex.Lock() + defer t.httpScaledObjectsMutex.Unlock() + + delete(t.httpScaledObjects, key) } diff --git a/pkg/routing/table_rpc.go b/pkg/routing/table_rpc.go deleted file mode 100644 index 4afb0c37c..000000000 --- a/pkg/routing/table_rpc.go +++ /dev/null @@ -1,93 +0,0 @@ -package routing - -import ( - "encoding/json" - "net/http" - - "github.com/go-logr/logr" - - "github.com/kedacore/http-add-on/pkg/k8s" - "github.com/kedacore/http-add-on/pkg/queue" -) - -const ( - routingPingPath = "/routing_ping" - routingFetchPath = "/routing_table" -) - -// AddFetchRoute adds a route to mux that fetches the current state of table, -// encodes it as JSON, and returns it to the HTTP client -func AddFetchRoute( - lggr logr.Logger, - mux *http.ServeMux, - table *Table, -) { - lggr = lggr.WithName("pkg.routing.AddFetchRoute") - lggr.Info("adding routing ping route", "path", routingPingPath) - mux.Handle(routingFetchPath, newTableHandler(lggr, table)) -} - -// AddPingRoute adds a route to mux that will accept an empty GET request, -// fetch the current state of the routing table from the standard routing -// table ConfigMap (ConfigMapRoutingTableName), save it to local memory, and -// return the contents of the routing table to the client. -func AddPingRoute( - lggr logr.Logger, - mux *http.ServeMux, - getter k8s.ConfigMapGetter, - table *Table, - q queue.Counter, -) { - lggr = lggr.WithName("pkg.routing.AddPingRoute") - lggr.Info("adding interceptor routing ping route", "path", routingPingPath) - mux.HandleFunc(routingPingPath, func(w http.ResponseWriter, r *http.Request) { - err := GetTable( - r.Context(), - lggr, - getter, - table, - q, - ) - if err != nil { - lggr.Error(err, "fetching new routing table") - w.WriteHeader(500) - if _, err := w.Write([]byte( - "error fetching routing table", - )); err != nil { - lggr.Error( - err, - "could not write error response to client", - ) - } - return - } - w.WriteHeader(200) - if err := json.NewEncoder(w).Encode(table); err != nil { - w.WriteHeader(500) - lggr.Error(err, "writing new routing table to the client") - return - } - }) -} - -func newTableHandler( - lggr logr.Logger, - table *Table, -) http.Handler { - lggr = lggr.WithName("pkg.routing.TableHandler") - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - err := json.NewEncoder(w).Encode(table) - if err != nil { - w.WriteHeader(500) - lggr.Error(err, "encoding logging table JSON") - if _, err := w.Write([]byte( - "error encoding and transmitting the routing table", - )); err != nil { - lggr.Error( - err, - "could not send error message to client", - ) - } - } - }) -} diff --git a/pkg/routing/table_rpc_test.go b/pkg/routing/table_rpc_test.go deleted file mode 100644 index 6297ff00d..000000000 --- a/pkg/routing/table_rpc_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package routing - -import ( - "context" - "testing" - - "github.com/go-logr/logr" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes/fake" - - "github.com/kedacore/http-add-on/pkg/queue" -) - -func newTableFromMap(r *require.Assertions, m map[string]Target) *Table { - table := NewTable() - for host, target := range m { - r.NoError(table.AddTarget(host, target)) - } - return table -} - -func TestRPCIntegration(t *testing.T) { - const ns = "testns" - ctx := context.Background() - lggr := logr.Discard() - r := require.New(t) - - // fetch an empty table - retTable := NewTable() - k8sCl, err := fakeConfigMapClientForTable( - NewTable(), - ns, - ConfigMapRoutingTableName, - ) - r.NoError(err) - r.NoError(GetTable( - ctx, - lggr, - k8sCl.CoreV1().ConfigMaps("testns"), - retTable, - queue.NewFakeCounter(), - )) - r.Equal(0, len(retTable.m)) - - // fetch a table with lots of targets in it - targetMap := map[string]Target{ - "host1": { - Service: "svc1", - Port: 1234, - Deployment: "depl1", - }, - "host2": { - Service: "svc2", - Port: 2345, - Deployment: "depl2", - }, - } - - retTable = NewTable() - k8sCl, err = fakeConfigMapClientForTable( - newTableFromMap(r, targetMap), - ns, - ConfigMapRoutingTableName, - ) - r.NoError(err) - r.NoError(GetTable( - ctx, - lggr, - k8sCl.CoreV1().ConfigMaps("testns"), - retTable, - queue.NewFakeCounter(), - )) - r.Equal(len(targetMap), len(retTable.m)) - r.Equal(targetMap, retTable.m) -} - -func fakeConfigMapClientForTable(t *Table, ns, name string) (*fake.Clientset, error) { - cm := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns, - }, - Data: map[string]string{}, - } - if err := SaveTableToConfigMap(t, cm); err != nil { - return nil, err - } - - return fake.NewSimpleClientset(cm), nil -} diff --git a/pkg/routing/table_test.go b/pkg/routing/table_test.go deleted file mode 100644 index 4e202d089..000000000 --- a/pkg/routing/table_test.go +++ /dev/null @@ -1,216 +0,0 @@ -package routing - -import ( - "encoding/json" - "math" - "math/rand" - "strconv" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/stretchr/testify/require" -) - -func TestTableJSONRoundTrip(t *testing.T) { - const ( - host = "testhost" - ns = "testns" - ) - r := require.New(t) - tbl := NewTable() - tgt := NewTarget( - ns, - "testsvc", - 8082, - "testdepl", - 1234, - ) - r.NoError(tbl.AddTarget(host, tgt)) - - b, err := json.Marshal(&tbl) - r.NoError(err) - - returnTbl := NewTable() - r.NoError(json.Unmarshal(b, returnTbl)) - retTarget, err := returnTbl.Lookup(host) - r.NoError(err) - r.Equal(tgt.Service, retTarget.Service) - r.Equal(tgt.Port, retTarget.Port) - r.Equal(tgt.Deployment, retTarget.Deployment) -} - -func TestTableRemove(t *testing.T) { - const ( - host = "testrm" - ns = "testns" - ) - - r := require.New(t) - tgt := NewTarget( - ns, - "testrm", - 8084, - "testrmdepl", - 1234, - ) - - tbl := NewTable() - - // add the target to the table and ensure that you can look it up - r.NoError(tbl.AddTarget(host, tgt)) - retTgt, err := tbl.Lookup(host) - r.Equal(&tgt, retTgt) - r.NoError(err) - - // remove the target and ensure that you can't look it up - r.NoError(tbl.RemoveTarget(host)) - retTgt, err = tbl.Lookup(host) - r.Equal((*Target)(nil), retTgt) - r.Equal(ErrTargetNotFound, err) -} - -func TestTableReplace(t *testing.T) { - const ns = "testns" - r := require.New(t) - const host1 = "testreplhost1" - const host2 = "testreplhost2" - tgt1 := NewTarget( - ns, - "tgt1", - 9090, - "depl1", - 1234, - ) - tgt2 := NewTarget( - ns, - "tgt2", - 9091, - "depl2", - 1234, - ) - // create two routing tables, each with different targets - tbl1 := NewTable() - r.NoError(tbl1.AddTarget(host1, tgt1)) - tbl2 := NewTable() - r.NoError(tbl2.AddTarget(host2, tgt2)) - - // replace the second table with the first and ensure that the tables - // are now equal - tbl2.Replace(tbl1) - - r.Equal(tbl1, tbl2) -} - -var _ = Describe("Table", func() { - Describe("Lookup", func() { - var ( - tltcs = newTableLookupTestCases(5) - table = NewTable() - ) - - Context("with new port-agnostic configuration", func() { - BeforeEach(func() { - for _, tltc := range tltcs { - err := table.AddTarget(tltc.HostWithoutPort(), tltc.Target()) - Expect(err).NotTo(HaveOccurred()) - } - }) - - AfterEach(func() { - for _, tltc := range tltcs { - err := table.RemoveTarget(tltc.HostWithoutPort()) - Expect(err).NotTo(HaveOccurred()) - } - }) - - It("should return correct target for host without port", func() { - for _, tltc := range tltcs { - target, err := table.Lookup(tltc.HostWithoutPort()) - Expect(err).NotTo(HaveOccurred()) - Expect(target).To(HaveValue(Equal(tltc.Target()))) - } - }) - - It("should return correct target for host with port", func() { - for _, tltc := range tltcs { - target, err := table.Lookup(tltc.HostWithPort()) - Expect(err).NotTo(HaveOccurred()) - Expect(target).To(HaveValue(Equal(tltc.Target()))) - } - }) - }) - - Context("with legacy port-specific configuration", func() { - BeforeEach(func() { - for _, tltc := range tltcs { - err := table.AddTarget(tltc.HostWithPort(), tltc.Target()) - Expect(err).NotTo(HaveOccurred()) - } - }) - - AfterEach(func() { - for _, tltc := range tltcs { - err := table.RemoveTarget(tltc.HostWithPort()) - Expect(err).NotTo(HaveOccurred()) - } - }) - - It("should error for host without port", func() { - for _, tltc := range tltcs { - target, err := table.Lookup(tltc.HostWithoutPort()) - Expect(err).To(MatchError(ErrTargetNotFound)) - Expect(target).To(BeNil()) - } - }) - - It("should return correct target for host with port", func() { - for _, tltc := range tltcs { - target, err := table.Lookup(tltc.HostWithPort()) - Expect(err).NotTo(HaveOccurred()) - Expect(target).To(HaveValue(Equal(tltc.Target()))) - } - }) - }) - }) -}) - -type tableLookupTestCase struct { - target Target -} - -func newTableLookupTestCase() tableLookupTestCase { - target := NewTarget( - strconv.Itoa(rand.Int()), - strconv.Itoa(rand.Int()), - rand.Intn(math.MaxUint16), - strconv.Itoa(rand.Int()), - int32(rand.Intn(math.MaxUint8)), - ) - - return tableLookupTestCase{ - target: target, - } -} - -func (tltc tableLookupTestCase) Target() Target { - return tltc.target -} - -func (tltc tableLookupTestCase) HostWithoutPort() string { - return tltc.target.Service + "." + tltc.target.Namespace + ".svc.cluster.local" -} - -func (tltc tableLookupTestCase) HostWithPort() string { - return tltc.HostWithoutPort() + ":" + strconv.Itoa(tltc.target.Port) -} - -type tableLookupTestCases []tableLookupTestCase - -func newTableLookupTestCases(count uint) tableLookupTestCases { - tltcs := make(tableLookupTestCases, count) - for i := uint(0); i < count; i++ { - tltcs[i] = newTableLookupTestCase() - } - return tltcs -} diff --git a/pkg/routing/tablememory.go b/pkg/routing/tablememory.go new file mode 100644 index 000000000..60f835f63 --- /dev/null +++ b/pkg/routing/tablememory.go @@ -0,0 +1,84 @@ +package routing + +import ( + "fmt" + "net/url" + "strings" + + iradix "github.com/hashicorp/go-immutable-radix/v2" + + httpv1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1" +) + +type TableMemory interface { + Remember(*httpv1alpha1.HTTPScaledObject) TableMemory + Forget(*httpv1alpha1.HTTPScaledObject) TableMemory + Recall(*httpv1alpha1.HTTPScaledObject) *httpv1alpha1.HTTPScaledObject + Route(url *url.URL) *httpv1alpha1.HTTPScaledObject +} + +type tableMemory struct { + tree *iradix.Tree[*httpv1alpha1.HTTPScaledObject] +} + +func NewTableMemory() TableMemory { + return tableMemory{ + tree: iradix.New[*httpv1alpha1.HTTPScaledObject](), + } +} + +var _ TableMemory = (*tableMemory)(nil) + +func (tm tableMemory) Remember(httpso *httpv1alpha1.HTTPScaledObject) TableMemory { + key := tm.treeKeyForHTTPSO(httpso) + tree, _, _ := tm.tree.Insert(key, httpso) + return tableMemory{tree} +} + +func (tm tableMemory) Forget(httpso *httpv1alpha1.HTTPScaledObject) TableMemory { + key := tm.treeKeyForHTTPSO(httpso) + tree, _, _ := tm.tree.Delete(key) + return tableMemory{tree} +} + +func (tm tableMemory) Recall(newHTTPSO *httpv1alpha1.HTTPScaledObject) *httpv1alpha1.HTTPScaledObject { + key := tm.treeKeyForHTTPSO(newHTTPSO) + curHTTPSO, _ := tm.tree.Get(key) + return curHTTPSO +} + +func (tm tableMemory) Route(url *url.URL) *httpv1alpha1.HTTPScaledObject { + key := tm.treeKeyForURL(url) + _, curHTTPSO, _ := tm.tree.Root().LongestPrefix(key) + return curHTTPSO +} + +func (tm tableMemory) treeKeyForURL(url *url.URL) []byte { + if url == nil { + return nil + } + return tm.treeKey(url.Host, url.Path) +} + +func (tm tableMemory) treeKeyForHTTPSO(httpso *httpv1alpha1.HTTPScaledObject) []byte { + if httpso == nil { + return nil + } + return tm.treeKey(httpso.Spec.Host, "" /* httpso.Spec.Path */) +} + +func (tm tableMemory) treeKey(host string, path string) []byte { + if i := strings.Index(host, ":"); i != -1 { + host = host[:i] + } + + for strings.HasPrefix(path, "/") { + path = path[1:] + } + if path != "" { + path = "/" + path + } + + key := fmt.Sprintf("//%s%s", host, path) + return []byte(key) +} diff --git a/pkg/routing/target.go b/pkg/routing/target.go deleted file mode 100644 index 49e2a48b4..000000000 --- a/pkg/routing/target.go +++ /dev/null @@ -1,59 +0,0 @@ -package routing - -import ( - "errors" - "fmt" - "net/url" -) - -// ErrTargetNotFound is returned when a target is not -// found in the table. -var ErrTargetNotFound = errors.New("Target not found") - -// Target is a single target in the routing table. -type Target struct { - Service string - Port int - Deployment string - Namespace string - TargetPendingRequests int32 -} - -// NewTarget creates a new Target from the given parameters. -func NewTarget( - namespace, - svc string, - port int, - depl string, - targetPendingReqs int32, -) Target { - return Target{ - Service: svc, - Port: port, - Deployment: depl, - TargetPendingRequests: targetPendingReqs, - Namespace: namespace, - } -} - -// ServiceURLFunc is a function that returns the full in-cluster -// URL for the given Target. -// -// ServiceURL is the production implementation of this function -type ServiceURLFunc func(Target) (*url.URL, error) - -// ServiceURL returns the full URL for the Kubernetes service, -// port and namespace of t. -func ServiceURL(t Target) (*url.URL, error) { - urlStr := fmt.Sprintf( - "http://%s.%s:%d", - t.Service, - t.Namespace, - t.Port, - ) - u, err := url.Parse(urlStr) - if err != nil { - return nil, err - } - return u, nil -} diff --git a/pkg/routing/target_test.go b/pkg/routing/target_test.go deleted file mode 100644 index ec271c7d3..000000000 --- a/pkg/routing/target_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package routing - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestTargetServiceURL(t *testing.T) { - r := require.New(t) - - target := NewTarget( - "testns", - "testsvc", - 8081, - "testdeploy", - 1234, - ) - svcURL, err := ServiceURL(target) - r.NoError(err) - r.Equal( - fmt.Sprintf("%s.%s:%d", target.Service, target.Namespace, target.Port), - svcURL.Host, - ) -} diff --git a/pkg/routing/util.go b/pkg/routing/util.go new file mode 100644 index 000000000..8e1143a7f --- /dev/null +++ b/pkg/routing/util.go @@ -0,0 +1,21 @@ +package routing + +import ( + "context" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func toNamespacedName(obj client.Object) types.NamespacedName { + return types.NamespacedName{ + Namespace: obj.GetNamespace(), + Name: obj.GetName(), + } +} + +func applyContext(ctx context.Context, f func(ctx context.Context) error) func() error { + return func() error { + return f(ctx) + } +} diff --git a/scaler/Dockerfile b/scaler/Dockerfile index d9575e8b0..5416a5975 100644 --- a/scaler/Dockerfile +++ b/scaler/Dockerfile @@ -1,28 +1,14 @@ -# Build the adapter binary -FROM --platform=$BUILDPLATFORM ghcr.io/kedacore/build-tools:1.19.5 as builder - -ARG VERSION=main -ARG GIT_COMMIT=HEAD - +FROM --platform=${BUILDPLATFORM} ghcr.io/kedacore/build-tools:1.20.4 as builder WORKDIR /workspace - -COPY go.mod go.mod -COPY go.sum go.sum +COPY go.* . RUN go mod download - COPY . . - -# Build -# https://www.docker.com/blog/faster-multi-platform-builds-dockerfile-cross-compilation-guide/ +ARG VERSION=main +ARG GIT_COMMIT=HEAD ARG TARGETOS ARG TARGETARCH -RUN VERSION=${VERSION} GIT_COMMIT=${GIT_COMMIT} TARGET_OS=$TARGETOS ARCH=$TARGETARCH make build-scaler +RUN VERSION="${VERSION}" GIT_COMMIT="${GIT_COMMIT}" TARGET_OS="${TARGETOS}" ARCH="${TARGETARCH}" make build-scaler FROM gcr.io/distroless/static:nonroot -WORKDIR / -COPY --from=builder /workspace/bin/scaler . -# 65532 is numeric for nonroot -USER 65532:65532 -EXPOSE 8080 - -ENTRYPOINT ["/scaler"] +COPY --from=builder /workspace/bin/scaler /sbin/init +ENTRYPOINT ["/sbin/init"] diff --git a/scaler/handlers.go b/scaler/handlers.go index f616fc240..7829e6a4d 100644 --- a/scaler/handlers.go +++ b/scaler/handlers.go @@ -5,15 +5,16 @@ package main import ( - context "context" + "context" "fmt" "math/rand" "time" "github.com/go-logr/logr" "google.golang.org/protobuf/types/known/emptypb" + "k8s.io/utils/pointer" - "github.com/kedacore/http-add-on/pkg/routing" + informershttpv1alpha1 "github.com/kedacore/http-add-on/operator/generated/informers/externalversions/http/v1alpha1" externalscaler "github.com/kedacore/http-add-on/proto" ) @@ -28,7 +29,7 @@ const ( type impl struct { lggr logr.Logger pinger *queuePinger - routingTable routing.TableReader + httpsoInformer informershttpv1alpha1.HTTPScaledObjectInformer targetMetric int64 targetMetricInterceptor int64 externalscaler.UnimplementedExternalScalerServer @@ -37,14 +38,14 @@ type impl struct { func newImpl( lggr logr.Logger, pinger *queuePinger, - routingTable routing.TableReader, + httpsoInformer informershttpv1alpha1.HTTPScaledObjectInformer, defaultTargetMetric int64, defaultTargetMetricInterceptor int64, ) *impl { return &impl{ lggr: lggr, pinger: pinger, - routingTable: routingTable, + httpsoInformer: httpsoInformer, targetMetric: defaultTargetMetric, targetMetricInterceptor: defaultTargetMetricInterceptor, } @@ -55,7 +56,7 @@ func (e *impl) Ping(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { } func (e *impl) IsActive( - ctx context.Context, + _ context.Context, scaledObject *externalscaler.ScaledObjectRef, ) (*externalscaler.IsActiveResponse, error) { lggr := e.lggr.WithName("IsActive") @@ -71,19 +72,7 @@ func (e *impl) IsActive( }, nil } - hostCount, ok := getHostCount( - host, - e.pinger.counts(), - e.routingTable, - ) - if !ok { - err := fmt.Errorf("host '%s' not found in counts", host) - allCounts := e.pinger.mergeCountsWithRoutingTable( - e.routingTable, - ) - lggr.Error(err, "Given host was not found in queue count map", "host", host, "allCounts", allCounts) - return nil, err - } + hostCount := e.pinger.counts()[host] active := hostCount > 0 return &externalscaler.IsActiveResponse{ Result: active, @@ -137,21 +126,15 @@ func (e *impl) GetMetricSpec( lggr.Error(err, "no 'host' found in ScaledObject metadata") return nil, err } - var targetPendingRequests int64 - if host == interceptor { - targetPendingRequests = e.targetMetricInterceptor - } else { - target, err := e.routingTable.Lookup(host) + targetPendingRequests := e.targetMetricInterceptor + if host != interceptor { + httpso, err := e.httpsoInformer.Lister().HTTPScaledObjects(sor.Namespace).Get(sor.Name) if err != nil { - lggr.Error( - err, - "error getting target for host", - "host", - host, - ) + lggr.Error(err, "unable to get HTTPScaledObject", "name", sor.Name, "namespace", sor.Namespace) return nil, err } - targetPendingRequests = int64(target.TargetPendingRequests) + + targetPendingRequests = int64(pointer.Int32Deref(httpso.Spec.TargetPendingRequests, 100)) } metricSpecs := []*externalscaler.MetricSpec{ { @@ -177,20 +160,9 @@ func (e *impl) GetMetrics( return nil, err } - hostCount, ok := getHostCount( - host, - e.pinger.counts(), - e.routingTable, - ) - if !ok { - if host == interceptor { - hostCount = e.pinger.aggregate() - } else { - err := fmt.Errorf("host '%s' not found in counts", host) - allCounts := e.pinger.mergeCountsWithRoutingTable(e.routingTable) - lggr.Error(err, "allCounts", allCounts) - return nil, err - } + hostCount, ok := e.pinger.counts()[host] + if !ok && host == interceptor { + hostCount = e.pinger.aggregate() } metricValues := []*externalscaler.MetricValue{ { diff --git a/scaler/handlers_test.go b/scaler/handlers_test.go index 8c0d95457..b8d7f1bb4 100644 --- a/scaler/handlers_test.go +++ b/scaler/handlers_test.go @@ -1,52 +1,47 @@ package main import ( - context "context" + "context" "fmt" "net" "testing" "time" "github.com/go-logr/logr" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/test/bufconn" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" + httpv1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1" + informersexternalversionshttpv1alpha1mock "github.com/kedacore/http-add-on/operator/generated/informers/externalversions/http/v1alpha1/mock" + listershttpv1alpha1mock "github.com/kedacore/http-add-on/operator/generated/listers/http/v1alpha1/mock" "github.com/kedacore/http-add-on/pkg/queue" - "github.com/kedacore/http-add-on/pkg/routing" externalscaler "github.com/kedacore/http-add-on/proto" ) -func standardTarget() routing.Target { - return routing.NewTarget( - "testns", - "testsrv", - 8080, - "testdepl", - 123, - ) -} - func TestStreamIsActive(t *testing.T) { type testCase struct { name string host string expected bool expectedErr bool - setup func(*routing.Table, *queuePinger) + setup func(*listershttpv1alpha1mock.MockHTTPScaledObjectNamespaceLister, *queuePinger) } - r := require.New(t) testCases := []testCase{ { name: "Simple host inactive", host: t.Name(), expected: false, expectedErr: false, - setup: func(table *routing.Table, q *queuePinger) { - r.NoError(table.AddTarget(t.Name(), standardTarget())) + setup: func(_ *listershttpv1alpha1mock.MockHTTPScaledObjectNamespaceLister, q *queuePinger) { q.pingMut.Lock() defer q.pingMut.Unlock() + q.allCounts[t.Name()] = 0 }, }, @@ -55,17 +50,17 @@ func TestStreamIsActive(t *testing.T) { host: "interceptor", expected: true, expectedErr: false, - setup: func(*routing.Table, *queuePinger) {}, + setup: func(_ *listershttpv1alpha1mock.MockHTTPScaledObjectNamespaceLister, _ *queuePinger) {}, }, { name: "Simple host active", host: t.Name(), expected: true, expectedErr: false, - setup: func(table *routing.Table, q *queuePinger) { - r.NoError(table.AddTarget(t.Name(), standardTarget())) + setup: func(_ *listershttpv1alpha1mock.MockHTTPScaledObjectNamespaceLister, q *queuePinger) { q.pingMut.Lock() defer q.pingMut.Unlock() + q.allCounts[t.Name()] = 1 }, }, @@ -74,34 +69,35 @@ func TestStreamIsActive(t *testing.T) { host: t.Name(), expected: false, expectedErr: false, - setup: func(table *routing.Table, q *queuePinger) { - r.NoError(table.AddTarget(t.Name(), standardTarget())) - }, + setup: func(_ *listershttpv1alpha1mock.MockHTTPScaledObjectNamespaceLister, _ *queuePinger) {}, }, { name: "Host doesn't exist", host: t.Name(), expected: false, expectedErr: true, - setup: func(*routing.Table, *queuePinger) {}, + setup: func(_ *listershttpv1alpha1mock.MockHTTPScaledObjectNamespaceLister, _ *queuePinger) {}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + r := require.New(t) ctx := context.Background() lggr := logr.Discard() - table := routing.NewTable() + informer, _, namespaceLister := newMocks(ctrl) ticker, pinger, err := newFakeQueuePinger(ctx, lggr) r.NoError(err) defer ticker.Stop() - tc.setup(table, pinger) + tc.setup(namespaceLister, pinger) hdl := newImpl( lggr, pinger, - table, + informer, 123, 200, ) @@ -166,19 +162,18 @@ func TestIsActive(t *testing.T) { host string expected bool expectedErr bool - setup func(*routing.Table, *queuePinger) + setup func(*listershttpv1alpha1mock.MockHTTPScaledObjectNamespaceLister, *queuePinger) } - r := require.New(t) testCases := []testCase{ { name: "Simple host inactive", host: t.Name(), expected: false, expectedErr: false, - setup: func(table *routing.Table, q *queuePinger) { - r.NoError(table.AddTarget(t.Name(), standardTarget())) + setup: func(namespaceLister *listershttpv1alpha1mock.MockHTTPScaledObjectNamespaceLister, q *queuePinger) { q.pingMut.Lock() defer q.pingMut.Unlock() + q.allCounts[t.Name()] = 0 }, }, @@ -187,17 +182,17 @@ func TestIsActive(t *testing.T) { host: "interceptor", expected: true, expectedErr: false, - setup: func(*routing.Table, *queuePinger) {}, + setup: func(_ *listershttpv1alpha1mock.MockHTTPScaledObjectNamespaceLister, _ *queuePinger) {}, }, { name: "Simple host active", host: t.Name(), expected: true, expectedErr: false, - setup: func(table *routing.Table, q *queuePinger) { - r.NoError(table.AddTarget(t.Name(), standardTarget())) + setup: func(namespaceLister *listershttpv1alpha1mock.MockHTTPScaledObjectNamespaceLister, q *queuePinger) { q.pingMut.Lock() defer q.pingMut.Unlock() + q.allCounts[t.Name()] = 1 }, }, @@ -206,33 +201,34 @@ func TestIsActive(t *testing.T) { host: t.Name(), expected: false, expectedErr: false, - setup: func(table *routing.Table, q *queuePinger) { - r.NoError(table.AddTarget(t.Name(), standardTarget())) - }, + setup: func(namespaceLister *listershttpv1alpha1mock.MockHTTPScaledObjectNamespaceLister, _ *queuePinger) {}, }, { name: "Host doesn't exist", host: t.Name(), expected: false, expectedErr: true, - setup: func(*routing.Table, *queuePinger) {}, + setup: func(_ *listershttpv1alpha1mock.MockHTTPScaledObjectNamespaceLister, _ *queuePinger) {}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + r := require.New(t) ctx := context.Background() lggr := logr.Discard() - table := routing.NewTable() + informer, _, namespaceLister := newMocks(ctrl) ticker, pinger, err := newFakeQueuePinger(ctx, lggr) r.NoError(err) defer ticker.Stop() - tc.setup(table, pinger) + tc.setup(namespaceLister, pinger) hdl := newImpl( lggr, pinger, - table, + informer, 123, 200, ) @@ -264,10 +260,9 @@ func TestGetMetricSpecTable(t *testing.T) { defaultTargetMetric int64 defaultTargetMetricInterceptor int64 scalerMetadata map[string]string - newRoutingTableFn func() *routing.Table + newInformer func(*gomock.Controller) *informersexternalversionshttpv1alpha1mock.MockHTTPScaledObjectInformer checker func(*testing.T, *externalscaler.GetMetricSpecResponse, error) } - r := require.New(t) cases := []testCase{ { name: "valid host as host value in scaler metadata", @@ -277,16 +272,28 @@ func TestGetMetricSpecTable(t *testing.T) { "host": "validHost", "targetPendingRequests": "123", }, - newRoutingTableFn: func() *routing.Table { - ret := routing.NewTable() - r.NoError(ret.AddTarget("validHost", routing.NewTarget( - ns, - "testsrv", - 8080, - "testdepl", - 123, - ))) - return ret + newInformer: func(ctrl *gomock.Controller) *informersexternalversionshttpv1alpha1mock.MockHTTPScaledObjectInformer { + informer, _, namespaceLister := newMocks(ctrl) + + name := "validHost" + httpso := &httpv1alpha1.HTTPScaledObject{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + }, + Spec: httpv1alpha1.HTTPScaledObjectSpec{ + ScaleTargetRef: &httpv1alpha1.ScaleTargetRef{ + Deployment: "testdepl", + Service: "testsrv", + Port: 8080, + }, + TargetPendingRequests: pointer.Int32(123), + }, + } + namespaceLister.EXPECT(). + Get(name). + Return(httpso, nil) + + return informer }, checker: func(t *testing.T, res *externalscaler.GetMetricSpecResponse, err error) { t.Helper() @@ -307,16 +314,9 @@ func TestGetMetricSpecTable(t *testing.T) { "host": "interceptor", "targetPendingRequests": "123", }, - newRoutingTableFn: func() *routing.Table { - ret := routing.NewTable() - r.NoError(ret.AddTarget("validHost", routing.NewTarget( - ns, - "testsrv", - 8080, - "testdepl", - 123, - ))) - return ret + newInformer: func(ctrl *gomock.Controller) *informersexternalversionshttpv1alpha1mock.MockHTTPScaledObjectInformer { + informer, _, _ := newMocks(ctrl) + return informer }, checker: func(t *testing.T, res *externalscaler.GetMetricSpecResponse, err error) { t.Helper() @@ -337,10 +337,13 @@ func TestGetMetricSpecTable(t *testing.T) { // in parallel testCase := c t.Run(testName, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + ctx := context.Background() t.Parallel() lggr := logr.Discard() - table := testCase.newRoutingTableFn() + informer := testCase.newInformer(ctrl) ticker, pinger, err := newFakeQueuePinger(ctx, lggr) if err != nil { t.Fatalf( @@ -352,11 +355,12 @@ func TestGetMetricSpecTable(t *testing.T) { hdl := newImpl( lggr, pinger, - table, + informer, testCase.defaultTargetMetric, testCase.defaultTargetMetricInterceptor, ) scaledObjectRef := externalscaler.ScaledObjectRef{ + Name: testCase.scalerMetadata["host"], ScalerMetadata: testCase.scalerMetadata, } ret, err := hdl.GetMetricSpec(ctx, &scaledObjectRef) @@ -370,9 +374,10 @@ func TestGetMetrics(t *testing.T) { name string scalerMetadata map[string]string setupFn func( + *gomock.Controller, context.Context, logr.Logger, - ) (*routing.Table, *queuePinger, func(), error) + ) (*informersexternalversionshttpv1alpha1mock.MockHTTPScaledObjectInformer, *queuePinger, func(), error) checkFn func(*testing.T, *externalscaler.GetMetricsResponse, error) defaultTargetMetric int64 defaultTargetMetricInterceptor int64 @@ -428,15 +433,18 @@ func TestGetMetrics(t *testing.T) { name: "no 'host' field in the scaler metadata field", scalerMetadata: map[string]string{}, setupFn: func( + ctrl *gomock.Controller, ctx context.Context, lggr logr.Logger, - ) (*routing.Table, *queuePinger, func(), error) { - table := routing.NewTable() + ) (*informersexternalversionshttpv1alpha1mock.MockHTTPScaledObjectInformer, *queuePinger, func(), error) { + informer, _, _ := newMocks(ctrl) + ticker, pinger, err := newFakeQueuePinger(ctx, lggr) if err != nil { return nil, nil, nil, err } - return table, pinger, func() { ticker.Stop() }, nil + + return informer, pinger, func() { ticker.Stop() }, nil }, checkFn: func(t *testing.T, res *externalscaler.GetMetricsResponse, err error) { t.Helper() @@ -457,23 +465,29 @@ func TestGetMetrics(t *testing.T) { "host": "missingHostInQueue", }, setupFn: func( + ctrl *gomock.Controller, ctx context.Context, lggr logr.Logger, - ) (*routing.Table, *queuePinger, func(), error) { - table := routing.NewTable() + ) (*informersexternalversionshttpv1alpha1mock.MockHTTPScaledObjectInformer, *queuePinger, func(), error) { + informer, _, _ := newMocks(ctrl) + // create queue and ticker without the host in it ticker, pinger, err := newFakeQueuePinger(ctx, lggr) if err != nil { return nil, nil, nil, err } - return table, pinger, func() { ticker.Stop() }, nil + + return informer, pinger, func() { ticker.Stop() }, nil }, checkFn: func(t *testing.T, res *externalscaler.GetMetricsResponse, err error) { t.Helper() r := require.New(t) - r.Error(err) - r.Contains(err.Error(), "host 'missingHostInQueue' not found in counts") - r.Nil(res) + r.NoError(err) + r.NotNil(res) + r.Equal(1, len(res.MetricValues)) + metricVal := res.MetricValues[0] + r.Equal("missingHostInQueue", metricVal.MetricName) + r.Equal(int64(0), metricVal.MetricValue) }, defaultTargetMetric: int64(200), defaultTargetMetricInterceptor: int64(300), @@ -484,10 +498,12 @@ func TestGetMetrics(t *testing.T) { "host": "validHost", }, setupFn: func( + ctrl *gomock.Controller, ctx context.Context, lggr logr.Logger, - ) (*routing.Table, *queuePinger, func(), error) { - table := routing.NewTable() + ) (*informersexternalversionshttpv1alpha1mock.MockHTTPScaledObjectInformer, *queuePinger, func(), error) { + informer, _, _ := newMocks(ctrl) + pinger, done, err := startFakeInterceptorServer(ctx, lggr, map[string]int{ "validHost": 201, }, 2*time.Millisecond) @@ -495,7 +511,7 @@ func TestGetMetrics(t *testing.T) { return nil, nil, nil, err } - return table, pinger, done, nil + return informer, pinger, done, nil }, checkFn: func(t *testing.T, res *externalscaler.GetMetricsResponse, err error) { t.Helper() @@ -516,10 +532,12 @@ func TestGetMetrics(t *testing.T) { "host": "interceptor", }, setupFn: func( + ctrl *gomock.Controller, ctx context.Context, lggr logr.Logger, - ) (*routing.Table, *queuePinger, func(), error) { - table := routing.NewTable() + ) (*informersexternalversionshttpv1alpha1mock.MockHTTPScaledObjectInformer, *queuePinger, func(), error) { + informer, _, _ := newMocks(ctrl) + pinger, done, err := startFakeInterceptorServer(ctx, lggr, map[string]int{ "host1": 201, "host2": 202, @@ -527,7 +545,8 @@ func TestGetMetrics(t *testing.T) { if err != nil { return nil, nil, nil, err } - return table, pinger, done, nil + + return informer, pinger, done, nil }, checkFn: func(t *testing.T, res *externalscaler.GetMetricsResponse, err error) { t.Helper() @@ -551,15 +570,12 @@ func TestGetMetrics(t *testing.T) { "host": "myhost.com", }, setupFn: func( + ctrl *gomock.Controller, ctx context.Context, lggr logr.Logger, - ) (*routing.Table, *queuePinger, func(), error) { - table := routing.NewTable() - r := require.New(t) - r.NoError(table.AddTarget( - "myhost.com", - standardTarget(), - )) + ) (*informersexternalversionshttpv1alpha1mock.MockHTTPScaledObjectInformer, *queuePinger, func(), error) { + informer, _, _ := newMocks(ctrl) + pinger, done, err := startFakeInterceptorServer(ctx, lggr, map[string]int{ "host1": 201, "host2": 202, @@ -567,7 +583,8 @@ func TestGetMetrics(t *testing.T) { if err != nil { return nil, nil, nil, err } - return table, pinger, done, nil + + return informer, pinger, done, nil }, checkFn: func(t *testing.T, res *externalscaler.GetMetricsResponse, err error) { t.Helper() @@ -591,19 +608,22 @@ func TestGetMetrics(t *testing.T) { tc := c name := fmt.Sprintf("test case %d: %s", i, tc.name) t.Run(name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + r := require.New(t) ctx, done := context.WithCancel( context.Background(), ) defer done() lggr := logr.Discard() - table, pinger, cleanup, err := tc.setupFn(ctx, lggr) + informer, pinger, cleanup, err := tc.setupFn(ctrl, ctx, lggr) r.NoError(err) defer cleanup() hdl := newImpl( lggr, pinger, - table, + informer, tc.defaultTargetMetric, tc.defaultTargetMetricInterceptor, ) @@ -616,3 +636,27 @@ func TestGetMetrics(t *testing.T) { }) } } + +func newMocks(ctrl *gomock.Controller) (*informersexternalversionshttpv1alpha1mock.MockHTTPScaledObjectInformer, *listershttpv1alpha1mock.MockHTTPScaledObjectLister, *listershttpv1alpha1mock.MockHTTPScaledObjectNamespaceLister) { + namespaceLister := listershttpv1alpha1mock.NewMockHTTPScaledObjectNamespaceLister(ctrl) + namespaceLister.EXPECT(). + Get(""). + DoAndReturn(func(name string) (*httpv1alpha1.HTTPScaledObject, error) { + return nil, errors.NewNotFound(httpv1alpha1.Resource("httpscaledobject"), name) + }). + AnyTimes() + + lister := listershttpv1alpha1mock.NewMockHTTPScaledObjectLister(ctrl) + lister.EXPECT(). + HTTPScaledObjects(gomock.Any()). + Return(namespaceLister). + AnyTimes() + + informer := informersexternalversionshttpv1alpha1mock.NewMockHTTPScaledObjectInformer(ctrl) + informer.EXPECT(). + Lister(). + Return(lister). + AnyTimes() + + return informer, lister, namespaceLister +} diff --git a/scaler/host_counts.go b/scaler/host_counts.go deleted file mode 100644 index 0a8874842..000000000 --- a/scaler/host_counts.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -import ( - "github.com/kedacore/http-add-on/pkg/routing" -) - -// getHostCount gets proper count for given host regardless whether -// host is in counts or only in routerTable -func getHostCount( - host string, - counts map[string]int, - table routing.TableReader, -) (int, bool) { - count, exists := counts[host] - if exists { - return count, exists - } - - exists = table.HasHost(host) - return 0, exists -} diff --git a/scaler/host_counts_test.go b/scaler/host_counts_test.go deleted file mode 100644 index f8b1c345a..000000000 --- a/scaler/host_counts_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/kedacore/http-add-on/pkg/routing" -) - -type testCase struct { - name string - table routing.TableReader - counts map[string]int - retCounts map[string]int -} - -func cases(r *require.Assertions) []testCase { - return []testCase{ - { - name: "empty queue", - table: newRoutingTable(r, []hostAndTarget{ - { - host: "www.example.com", - target: routing.Target{}, - }, - { - host: "www.example2.com", - target: routing.Target{}, - }, - }), - counts: make(map[string]int), - retCounts: map[string]int{ - "www.example.com": 0, - "www.example2.com": 0, - }, - }, - { - name: "one entry in queue, same entry in routing table", - table: newRoutingTable(r, []hostAndTarget{ - { - host: "example.com", - target: routing.Target{}, - }, - }), - counts: map[string]int{ - "example.com": 1, - }, - retCounts: map[string]int{ - "example.com": 1, - }, - }, - { - name: "one entry in queue, two in routing table", - table: newRoutingTable(r, []hostAndTarget{ - { - host: "example.com", - target: routing.Target{}, - }, - { - host: "example2.com", - target: routing.Target{}, - }, - }), - counts: map[string]int{ - "example.com": 1, - }, - retCounts: map[string]int{ - "example.com": 1, - "example2.com": 0, - }, - }, - } -} -func TestGetHostCount(t *testing.T) { - r := require.New(t) - for _, tc := range cases(r) { - for host, retCount := range tc.retCounts { - t.Run(tc.name, func(t *testing.T) { - r := require.New(t) - ret, exists := getHostCount( - host, - tc.counts, - tc.table, - ) - r.True(exists) - r.Equal(retCount, ret) - }) - } - } -} - -type hostAndTarget struct { - host string - target routing.Target -} - -func newRoutingTable(r *require.Assertions, entries []hostAndTarget) *routing.Table { - ret := routing.NewTable() - for _, entry := range entries { - r.NoError(ret.AddTarget(entry.host, entry.target)) - } - return ret -} diff --git a/scaler/main.go b/scaler/main.go index 04c577bd1..d81627eac 100644 --- a/scaler/main.go +++ b/scaler/main.go @@ -6,11 +6,9 @@ package main import ( "context" - "encoding/json" "fmt" "log" "net" - "net/http" "os" "time" @@ -18,31 +16,31 @@ import ( "golang.org/x/sync/errgroup" "google.golang.org/grpc" "google.golang.org/grpc/reflection" + "k8s.io/client-go/kubernetes" + ctrl "sigs.k8s.io/controller-runtime" + clientset "github.com/kedacore/http-add-on/operator/generated/clientset/versioned" + informers "github.com/kedacore/http-add-on/operator/generated/informers/externalversions" + informershttpv1alpha1 "github.com/kedacore/http-add-on/operator/generated/informers/externalversions/http/v1alpha1" "github.com/kedacore/http-add-on/pkg/build" - kedahttp "github.com/kedacore/http-add-on/pkg/http" "github.com/kedacore/http-add-on/pkg/k8s" pkglog "github.com/kedacore/http-add-on/pkg/log" - "github.com/kedacore/http-add-on/pkg/routing" externalscaler "github.com/kedacore/http-add-on/proto" ) // +kubebuilder:rbac:groups="",namespace=keda,resources=configmaps,verbs=get;list;watch // +kubebuilder:rbac:groups="",resources=endpoints,verbs=get;list;watch // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch +// +kubebuilder:rbac:groups=http.keda.sh,resources=httpscaledobjects,verbs=get;list;watch func main() { lggr, err := pkglog.NewZapr() if err != nil { log.Fatalf("error creating new logger (%v)", err) } - ctx, done := context.WithCancel( - context.Background(), - ) cfg := mustParseConfig() grpcPort := cfg.GRPCPort - healthPort := cfg.HealthPort namespace := cfg.TargetNamespace svcName := cfg.TargetService deplName := cfg.TargetDeployment @@ -50,10 +48,14 @@ func main() { targetPendingRequests := cfg.TargetPendingRequests targetPendingRequestsInterceptor := cfg.TargetPendingRequestsInterceptor - k8sCl, _, err := k8s.NewClientset() + k8sCfg, err := ctrl.GetConfig() if err != nil { - lggr.Error(err, "getting a Kubernetes client") - done() + lggr.Error(err, "Kubernetes client config not found") + os.Exit(1) + } + k8sCl, err := kubernetes.NewForConfig(k8sCfg) + if err != nil { + lggr.Error(err, "creating new Kubernetes ClientSet") os.Exit(1) } pinger, err := newQueuePinger( @@ -67,32 +69,9 @@ func main() { ) if err != nil { lggr.Error(err, "creating a queue pinger") - done() os.Exit(1) } - defer done() - - // This callback function is used to fetch and save - // the current queue counts from the interceptor immediately - // after updating the routingTable information. - callbackWhenRoutingTableUpdate := func() error { - if err := pinger.fetchAndSaveCounts(ctx); err != nil { - return err - } - return nil - } - table := routing.NewTable() - - // Create the informer of ConfigMap resource, - // the resynchronization period of the informer should be not less than 1s, - // refer to: https://github.com/kubernetes/client-go/blob/v0.22.2/tools/cache/shared_informer.go#L475 - configMapInformer := k8s.NewInformerConfigMapUpdater( - lggr, - k8sCl, - cfg.ConfigMapCacheRsyncPeriod, - cfg.TargetNamespace, - ) // create the deployment informer deployInformer := k8s.NewInformerBackedDeploymentCache( lggr, @@ -100,6 +79,19 @@ func main() { cfg.DeploymentCacheRsyncPeriod, ) + httpCl, err := clientset.NewForConfig(k8sCfg) + if err != nil { + lggr.Error(err, "creating new HTTP ClientSet") + os.Exit(1) + } + sharedInformerFactory := informers.NewSharedInformerFactory(httpCl, cfg.ConfigMapCacheRsyncPeriod) + httpsoInformer := informershttpv1alpha1.New(sharedInformerFactory, "", nil).HTTPScaledObjects() + + ctx, done := context.WithCancel( + context.Background(), + ) + defer done() + grp, ctx := errgroup.WithContext(ctx) // start the deployment informer @@ -108,6 +100,13 @@ func main() { return deployInformer.Start(ctx) }) + // start the httpso informer + grp.Go(func() error { + defer done() + httpsoInformer.Informer().Run(ctx.Done()) + return ctx.Err() + }) + grp.Go(func() error { defer done() return pinger.start( @@ -124,34 +123,12 @@ func main() { lggr, grpcPort, pinger, - table, + httpsoInformer, int64(targetPendingRequests), int64(targetPendingRequestsInterceptor), ) }) - grp.Go(func() error { - defer done() - return routing.StartConfigMapRoutingTableUpdater( - ctx, - lggr, - configMapInformer, - cfg.TargetNamespace, - table, - callbackWhenRoutingTableUpdate, - ) - }) - - grp.Go(func() error { - defer done() - return startAdminServer( - ctx, - lggr, - cfg, - healthPort, - pinger, - ) - }) build.PrintComponentInfo(lggr, "Scaler") lggr.Error(grp.Wait(), "one or more of the servers failed") } @@ -161,7 +138,7 @@ func startGrpcServer( lggr logr.Logger, port int, pinger *queuePinger, - routingTable *routing.Table, + httpsoInformer informershttpv1alpha1.HTTPScaledObjectInformer, targetPendingRequests int64, targetPendingRequestsInterceptor int64, ) error { @@ -178,7 +155,7 @@ func startGrpcServer( newImpl( lggr, pinger, - routingTable, + httpsoInformer, targetPendingRequests, targetPendingRequestsInterceptor, ), @@ -190,56 +167,3 @@ func startGrpcServer( }() return grpcServer.Serve(lis) } - -func startAdminServer( - ctx context.Context, - lggr logr.Logger, - cfg *config, - port int, - pinger *queuePinger, -) error { - lggr = lggr.WithName("startHealthcheckServer") - - mux := http.NewServeMux() - mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(200) - }) - mux.HandleFunc("/livez", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(200) - }) - mux.HandleFunc("/queue", func(w http.ResponseWriter, r *http.Request) { - lggr = lggr.WithName("route.counts") - cts := pinger.counts() - lggr.Info("counts endpoint", "counts", cts) - if err := json.NewEncoder(w).Encode(&cts); err != nil { - lggr.Error(err, "writing counts information to client") - w.WriteHeader(500) - } - }) - mux.HandleFunc("/queue_ping", func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - lggr := lggr.WithName("route.counts_ping") - if err := pinger.fetchAndSaveCounts(ctx); err != nil { - lggr.Error(err, "requesting counts failed") - w.WriteHeader(500) - _, err := w.Write([]byte("error requesting counts from interceptors")) - lggr.Error(err, "failed sending equesting counts failed") - return - } - cts := pinger.counts() - lggr.Info("counts ping endpoint", "counts", cts) - if err := json.NewEncoder(w).Encode(&cts); err != nil { - lggr.Error(err, "writing counts data to caller") - w.WriteHeader(500) - _, err := w.Write([]byte("error writing counts data to caller")) - lggr.Error(err, "failed sending writing counts data to caller") - } - }) - - kedahttp.AddConfigEndpoint(lggr, mux, cfg) - kedahttp.AddVersionEndpoint(lggr.WithName("scalerAdmin"), mux) - - addr := fmt.Sprintf("0.0.0.0:%d", port) - lggr.Info("starting health check server", "addr", addr) - return kedahttp.ServeContext(ctx, addr, mux) -} diff --git a/scaler/queue_pinger.go b/scaler/queue_pinger.go index d74463dcf..25dfddb71 100644 --- a/scaler/queue_pinger.go +++ b/scaler/queue_pinger.go @@ -14,7 +14,6 @@ import ( "github.com/kedacore/http-add-on/pkg/k8s" "github.com/kedacore/http-add-on/pkg/queue" - "github.com/kedacore/http-add-on/pkg/routing" ) // queuePinger has functionality to ping all interceptors @@ -128,23 +127,6 @@ func (q *queuePinger) counts() map[string]int { return q.allCounts } -// mergeCountsWithRoutingTable ensures that all hosts in routing table -// are present in combined counts, if count is not present value is set to 0 -func (q *queuePinger) mergeCountsWithRoutingTable( - table routing.TableReader, -) map[string]int { - q.pingMut.RLock() - defer q.pingMut.RUnlock() - mergedCounts := make(map[string]int) - for _, host := range table.Hosts() { - mergedCounts[host] = 0 - } - for key, value := range q.allCounts { - mergedCounts[key] = value - } - return mergedCounts -} - func (q *queuePinger) aggregate() int { q.pingMut.RLock() defer q.pingMut.RUnlock() @@ -207,7 +189,7 @@ func fetchCounts( countsCh := make(chan *queue.Counts) var wg sync.WaitGroup - fetchGrp, ctx := errgroup.WithContext(ctx) + fetchGrp, _ := errgroup.WithContext(ctx) for _, endpoint := range endpointURLs { // capture the endpoint in a loop-local // variable so that the goroutine can @@ -220,8 +202,6 @@ func fetchCounts( wg.Add(1) fetchGrp.Go(func() error { counts, err := queue.GetCounts( - ctx, - lggr, http.DefaultClient, *u, ) diff --git a/scaler/queue_pinger_fake.go b/scaler/queue_pinger_fake.go deleted file mode 100644 index ef8428ba3..000000000 --- a/scaler/queue_pinger_fake.go +++ /dev/null @@ -1,87 +0,0 @@ -package main - -import ( - context "context" - "net/http" - "net/http/httptest" - "net/url" - "time" - - "github.com/go-logr/logr" - v1 "k8s.io/api/core/v1" - - "github.com/kedacore/http-add-on/pkg/k8s" - kedanet "github.com/kedacore/http-add-on/pkg/net" - "github.com/kedacore/http-add-on/pkg/queue" -) - -// startFakeQueuePinger starts a fake server that simulates -// an interceptor with its /queue endpoint, then returns a -// *v1.Endpoints object that contains the URL of the new fake -// server. also returns the *httptest.Server that runs the -// endpoint along with its URL. the caller is responsible for -// calling testServer.Close() when done. -// -// returns nil for the first 3 return value and a non-nil error in -// case of a failure. -func startFakeQueueEndpointServer( - svcName string, - q queue.CountReader, - numEndpoints int, -) (*httptest.Server, *url.URL, *v1.Endpoints, error) { - hdl := http.NewServeMux() - queue.AddCountsRoute(logr.Discard(), hdl, q) - srv, srvURL, err := kedanet.StartTestServer(hdl) - if err != nil { - return nil, nil, nil, err - } - endpoints, err := k8s.FakeEndpointsForURL(srvURL, "testns", svcName, numEndpoints) - if err != nil { - return nil, nil, nil, err - } - return srv, srvURL, endpoints, nil -} - -type fakeQueuePingerOpts struct { - endpoints *v1.Endpoints - tickDur time.Duration - port string -} - -type optsFunc func(*fakeQueuePingerOpts) - -// newFakeQueuePinger creates the machinery required for a fake -// queuePinger implementation, including a time.Ticker, then returns -// the ticker and the pinger. it is the caller's responsibility to -// call ticker.Stop() on the returned ticker. -func newFakeQueuePinger( - ctx context.Context, - lggr logr.Logger, - optsFuncs ...optsFunc, -) (*time.Ticker, *queuePinger, error) { - opts := &fakeQueuePingerOpts{ - endpoints: &v1.Endpoints{}, - tickDur: time.Second, - port: "8080", - } - for _, optsFunc := range optsFuncs { - optsFunc(opts) - } - ticker := time.NewTicker(opts.tickDur) - - pinger, err := newQueuePinger( - ctx, - lggr, - func(context.Context, string, string) (*v1.Endpoints, error) { - return opts.endpoints, nil - }, - "testns", - "testsvc", - "testdepl", - opts.port, - ) - if err != nil { - return nil, nil, err - } - return ticker, pinger, nil -} diff --git a/scaler/queue_pinger_test.go b/scaler/queue_pinger_test.go index 0b433c9bc..2159ed702 100644 --- a/scaler/queue_pinger_test.go +++ b/scaler/queue_pinger_test.go @@ -1,16 +1,19 @@ package main import ( - context "context" + "context" + "net/http" + "net/http/httptest" + "net/url" "testing" "time" "github.com/go-logr/logr" "github.com/stretchr/testify/require" - "golang.org/x/sync/errgroup" v1 "k8s.io/api/core/v1" "github.com/kedacore/http-add-on/pkg/k8s" + kedanet "github.com/kedacore/http-add-on/pkg/net" "github.com/kedacore/http-add-on/pkg/queue" ) @@ -217,48 +220,73 @@ func TestFetchCounts(t *testing.T) { r.Equal(expectedCounts, cts) } -func TestMergeCountsWithRoutingTable(t *testing.T) { - r := require.New(t) - for _, tc := range cases(r) { - t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() - grp, ctx := errgroup.WithContext(ctx) - r := require.New(t) - const C = 100 - tickr, q, err := newFakeQueuePinger( - ctx, - logr.Discard(), - ) - r.NoError(err) - defer tickr.Stop() - q.allCounts = tc.counts +// startFakeQueuePinger starts a fake server that simulates +// an interceptor with its /queue endpoint, then returns a +// *v1.Endpoints object that contains the URL of the new fake +// server. also returns the *httptest.Server that runs the +// endpoint along with its URL. the caller is responsible for +// calling testServer.Close() when done. +// +// returns nil for the first 3 return value and a non-nil error in +// case of a failure. +func startFakeQueueEndpointServer( + svcName string, + q queue.CountReader, + numEndpoints int, +) (*httptest.Server, *url.URL, *v1.Endpoints, error) { + hdl := http.NewServeMux() + queue.AddCountsRoute(logr.Discard(), hdl, q) + srv, srvURL, err := kedanet.StartTestServer(hdl) + if err != nil { + return nil, nil, nil, err + } + endpoints, err := k8s.FakeEndpointsForURL(srvURL, "testns", svcName, numEndpoints) + if err != nil { + return nil, nil, nil, err + } + return srv, srvURL, endpoints, nil +} - retCh := make(chan map[string]int) - for i := 0; i < C; i++ { - grp.Go(func() error { - retCh <- q.mergeCountsWithRoutingTable(tc.table) - return nil - }) - } +type fakeQueuePingerOpts struct { + endpoints *v1.Endpoints + tickDur time.Duration + port string +} - // ensure we receive from retCh C times - allRets := map[int]map[string]int{} - for i := 0; i < C; i++ { - allRets[i] = <-retCh - } +type optsFunc func(*fakeQueuePingerOpts) - r.NoError(grp.Wait()) +// newFakeQueuePinger creates the machinery required for a fake +// queuePinger implementation, including a time.Ticker, then returns +// the ticker and the pinger. it is the caller's responsibility to +// call ticker.Stop() on the returned ticker. +func newFakeQueuePinger( + ctx context.Context, + lggr logr.Logger, + optsFuncs ...optsFunc, +) (*time.Ticker, *queuePinger, error) { + opts := &fakeQueuePingerOpts{ + endpoints: &v1.Endpoints{}, + tickDur: time.Second, + port: "8080", + } + for _, optsFunc := range optsFuncs { + optsFunc(opts) + } + ticker := time.NewTicker(opts.tickDur) - // ensure that all returned maps are the - // same - prev := allRets[0] - for i := 1; i < C; i++ { - r.Equal(prev, allRets[i]) - prev = allRets[i] - } - // ensure that all the returned maps are - // equal to what we expected - r.Equal(tc.retCounts, prev) - }) + pinger, err := newQueuePinger( + ctx, + lggr, + func(context.Context, string, string) (*v1.Endpoints, error) { + return opts.endpoints, nil + }, + "testns", + "testsvc", + "testdepl", + opts.port, + ) + if err != nil { + return nil, nil, err } + return ticker, pinger, nil } diff --git a/tests/e2e-test.sh b/tests/e2e-test.sh index 8df68e119..f388d5bea 100755 --- a/tests/e2e-test.sh +++ b/tests/e2e-test.sh @@ -41,6 +41,9 @@ helm upgrade --install keda kedacore/keda --namespace keda --create-namespace -- # Install Http-add-on make deploy +# Give a minute for pods to become ready +sleep 60 + # Show Kubernetes resources in keda namespace kubectl get all --namespace keda @@ -48,15 +51,18 @@ kubectl get all --namespace keda kubectl create ns app helm upgrade --install xkcd ./examples/xkcd -n app --wait +# Give a minute for resources to be created +sleep 60 + # Show Kubernetes resources in app namespace -kubectl get all --namespace app +kubectl get all,httpso,so --namespace app # Check http-add-on generated ScaledObject n=0 max=5 until [ "$n" -ge "$max" ] do - ready=$(kubectl get so xkcd-app -n app -o jsonpath="{.status.conditions[0].status}") + ready=$(kubectl get so xkcd -n app -o jsonpath="{.status.conditions[0].status}") echo "ready: $ready" if [ "$ready" == "True" ]; then break