├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── cmd └── gpa │ ├── Dockerfile │ ├── app │ ├── default.go │ └── options.go │ ├── main.go │ └── validator │ ├── options.go │ └── server.go ├── docs ├── autoscaler.png └── gs_scaledown.png ├── examples ├── metric.yaml ├── metric_custom.yaml ├── time.yaml └── webhook.yaml ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt ├── tools.go ├── update-codegen.sh └── verify-codegen.sh ├── manifeasts ├── crd.yaml ├── gpa.yaml └── validatorconfig.yaml └── pkg ├── apis ├── autoscaling │ ├── register.go │ └── v1alpha1 │ │ ├── doc.go │ │ ├── register.go │ │ ├── types.go │ │ └── zz_generated.deepcopy.go └── config │ ├── register.go │ └── v1alpha1 │ ├── doc.go │ ├── register.go │ ├── types.go │ └── zz_generated.deepcopy.go ├── client ├── clientset │ └── versioned │ │ ├── clientset.go │ │ ├── doc.go │ │ ├── fake │ │ ├── clientset_generated.go │ │ ├── doc.go │ │ └── register.go │ │ ├── scheme │ │ ├── doc.go │ │ └── register.go │ │ └── typed │ │ └── autoscaling │ │ └── v1alpha1 │ │ ├── autoscaling_client.go │ │ ├── doc.go │ │ ├── fake │ │ ├── doc.go │ │ ├── fake_autoscaling_client.go │ │ └── fake_generalpodautoscaler.go │ │ ├── generalpodautoscaler.go │ │ └── generated_expansion.go ├── informers │ └── externalversions │ │ ├── autoscaling │ │ ├── interface.go │ │ └── v1alpha1 │ │ │ ├── generalpodautoscaler.go │ │ │ └── interface.go │ │ ├── factory.go │ │ ├── generic.go │ │ └── internalinterfaces │ │ └── factory_interfaces.go └── listers │ └── autoscaling │ └── v1alpha1 │ ├── expansion_generated.go │ └── generalpodautoscaler.go ├── metrics ├── interfaces.go ├── legacy_metrics_client.go ├── metrics.go ├── prometheus_metrics.go ├── rest_metrics_client.go └── utilization.go ├── requests └── api.go ├── scaler ├── general.go ├── general_test.go ├── rate_limiters.go ├── replica_calculator.go ├── replica_calculator_test.go └── utils.go ├── scalercore ├── cron.go ├── cron_test.go ├── event.go ├── scaler.go └── webhook.go ├── util ├── patch.go └── signal.go ├── validation └── validation.go ├── validator └── hook.go └── version └── version.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.15 20 | 21 | - name: Build 22 | run: make build 23 | 24 | - name: Test 25 | run: make test 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX leaves these everywhere on SMB shares 2 | ._* 3 | 4 | # OSX trash 5 | .DS_Store 6 | 7 | # Eclipse files 8 | .classpath 9 | .project 10 | .settings/** 11 | 12 | # Files generated by JetBrains IDEs, e.g. IntelliJ IDEA 13 | .idea/ 14 | *.iml 15 | 16 | # Makefile temporary files 17 | Makefile.tmp 18 | 19 | # Vscode files 20 | .vscode 21 | 22 | # This is where the result of the go build goes 23 | /output*/ 24 | /_output*/ 25 | /_output 26 | 27 | # Emacs save files 28 | *~ 29 | \#*\# 30 | .\#* 31 | 32 | # Vim-related files 33 | [._]*.s[a-w][a-z] 34 | [._]s[a-w][a-z] 35 | *.un~ 36 | Session.vim 37 | .netrwhist 38 | 39 | # cscope-related files 40 | cscope.* 41 | 42 | # Go test binaries 43 | *.test 44 | /hack/.test-cmd-auth 45 | 46 | # JUnit test output from ginkgo e2e tests 47 | /junit*.xml 48 | 49 | # Mercurial files 50 | **/.hg 51 | **/.hg* 52 | 53 | # Vagrant 54 | .vagrant 55 | network_closure.sh 56 | 57 | # Local cluster env variables 58 | /cluster/env.sh 59 | 60 | # Compiled binaries in third_party 61 | /third_party/pkg 62 | 63 | # Also ignore etcd installed by hack/install-etcd.sh 64 | /third_party/etcd* 65 | /default.etcd 66 | 67 | # User cluster configs 68 | .kubeconfig 69 | 70 | .tags* 71 | 72 | # Version file for dockerized build 73 | .dockerized-kube-version-defs 74 | 75 | # Web UI 76 | /www/master/node_modules/ 77 | /www/master/npm-debug.log 78 | /www/master/shared/config/development.json 79 | 80 | # Karma output 81 | /www/test_out 82 | 83 | # precommit temporary directories created by ./hack/verify-generated-docs.sh and ./hack/lib/util.sh 84 | /_tmp/ 85 | /doc_tmp/ 86 | 87 | # Test artifacts produced by Jenkins jobs 88 | /_artifacts/ 89 | 90 | # Go dependencies installed on Jenkins 91 | /_gopath/ 92 | 93 | # Config directories created by gcloud and gsutil on Jenkins 94 | /.config/gcloud*/ 95 | /.gsutil/ 96 | 97 | # CoreOS stuff 98 | /cluster/libvirt-coreos/coreos_*.img 99 | 100 | # Juju Stuff 101 | /cluster/juju/charms/* 102 | /cluster/juju/bundles/local.yaml 103 | 104 | # Downloaded Kubernetes binary release 105 | /kubernetes/ 106 | 107 | # direnv .envrc files 108 | .envrc 109 | 110 | # Downloaded kubernetes binary release tar ball 111 | kubernetes.tar.gz 112 | 113 | # generated files in any directory 114 | # TODO(thockin): uncomment this when we stop committing the generated files. 115 | #zz_generated.* 116 | zz_generated.openapi.go 117 | 118 | # make-related metadata 119 | /.make/ 120 | # Just in time generated data in the source, should never be commited 121 | /test/e2e/generated/bindata.go 122 | 123 | # This file used by some vendor repos (e.g. github.com/go-openapi/...) to store secret variables and should not be ignored 124 | !\.drone\.sec 125 | 126 | # Godeps workspace 127 | /Godeps/_workspace 128 | 129 | /bazel-* 130 | *.pyc 131 | 132 | # used for the code generators only 133 | /vendor/ 134 | 135 | # ignore binary 136 | /bin/ 137 | 138 | ./idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | REGISTRY_NAME=docker.io/ocgi 17 | GIT_COMMIT=$(shell git rev-parse "HEAD^{commit}") 18 | VERSION=$(shell git describe --tags --abbrev=14 "${GIT_COMMIT}^{commit}" --always) 19 | BUILD_TIME=$(shell TZ=Asia/Shanghai date +%FT%T%z) 20 | VERSION_KEY=github.com/ocgi/general-pod-autoscaler/pkg/version.Version 21 | COMMIT_KEY=github.com/ocgi/general-pod-autoscaler/pkg/version.Commit 22 | 23 | CMDS=build 24 | all: test build 25 | 26 | build: vet fmt build-gpa 27 | 28 | build-gpa: 29 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X '$(VERSION_KEY)=$(VERSION)' -X '$(COMMIT_KEY)=$(GIT_COMMIT)'" -o ./bin/gpa ./cmd/gpa 30 | 31 | container: build 32 | docker build -t $(REGISTRY_NAME)/gpa:$(VERSION) -f $(shell if [ -e ./cmd/gpa/Dockerfile ]; then echo ./cmd/gpa/Dockerfile; else echo Dockerfile; fi) --label revision=$(REV) . 33 | 34 | push: container 35 | docker push $(REGISTRY_NAME)/gpa:$(VERSION) 36 | 37 | test: 38 | go test ./pkg/... 39 | 40 | autogen: 41 | go mod vendor 42 | bash hack/update-codegen.sh 43 | 44 | vet: 45 | go vet ./pkg/... 46 | 47 | fmt: 48 | go fmt ./pkg/... 49 | 50 | clean: 51 | rm -r ./bin 52 | -------------------------------------------------------------------------------- /cmd/gpa/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:centos7 2 | LABEL description="tenc gpa" 3 | 4 | COPY ./bin/gpa gpa 5 | ENTRYPOINT ["/gpa"] 6 | -------------------------------------------------------------------------------- /cmd/gpa/app/default.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // Copyright 2021 The Kubernetes Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package app 17 | 18 | import ( 19 | "time" 20 | 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | 23 | "github.com/ocgi/general-pod-autoscaler/pkg/apis/config/v1alpha1" 24 | ) 25 | 26 | func RecommendedDefaultGPAControllerConfiguration(obj *v1alpha1.GPAControllerConfiguration) { 27 | zero := metav1.Duration{} 28 | if obj.GeneralPodAutoscalerSyncPeriod == zero { 29 | obj.GeneralPodAutoscalerSyncPeriod = metav1.Duration{Duration: 15 * time.Second} 30 | } 31 | if obj.GeneralPodAutoscalerUpscaleForbiddenWindow == zero { 32 | obj.GeneralPodAutoscalerUpscaleForbiddenWindow = metav1.Duration{Duration: 3 * time.Minute} 33 | } 34 | if obj.GeneralPodAutoscalerDownscaleStabilizationWindow == zero { 35 | obj.GeneralPodAutoscalerDownscaleStabilizationWindow = metav1.Duration{Duration: 5 * time.Minute} 36 | } 37 | if obj.GeneralPodAutoscalerCPUInitializationPeriod == zero { 38 | obj.GeneralPodAutoscalerCPUInitializationPeriod = metav1.Duration{Duration: 5 * time.Minute} 39 | } 40 | if obj.GeneralPodAutoscalerInitialReadinessDelay == zero { 41 | obj.GeneralPodAutoscalerInitialReadinessDelay = metav1.Duration{Duration: 30 * time.Second} 42 | } 43 | if obj.GeneralPodAutoscalerDownscaleForbiddenWindow == zero { 44 | obj.GeneralPodAutoscalerDownscaleForbiddenWindow = metav1.Duration{Duration: 5 * time.Minute} 45 | } 46 | if obj.GeneralPodAutoscalerTolerance == 0 { 47 | obj.GeneralPodAutoscalerTolerance = 0.1 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /cmd/gpa/app/options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package app 16 | 17 | import ( 18 | "time" 19 | 20 | "github.com/spf13/pflag" 21 | "k8s.io/client-go/rest" 22 | "k8s.io/client-go/tools/clientcmd" 23 | 24 | "github.com/ocgi/general-pod-autoscaler/pkg/apis/config/v1alpha1" 25 | ) 26 | 27 | type RunOptions struct { 28 | KubeconfigPath string 29 | MasterUrl string 30 | QPS int 31 | Burst int 32 | Resync time.Duration 33 | ElectionName string 34 | ElectionNamespace string 35 | ElectionResourceLock string 36 | *v1alpha1.GPAControllerConfiguration 37 | } 38 | 39 | func NewServerRunOptions() *RunOptions { 40 | options := &RunOptions{GPAControllerConfiguration: &v1alpha1.GPAControllerConfiguration{}} 41 | options.addKubeFlags() 42 | options.addElectionFlags() 43 | options.addGPAFlags() 44 | RecommendedDefaultGPAControllerConfiguration(options.GPAControllerConfiguration) 45 | return options 46 | } 47 | 48 | func (s *RunOptions) addKubeFlags() { 49 | pflag.DurationVar(&s.Resync, "resync", 10*time.Minute, "Time to resync from apiserver.") 50 | pflag.StringVar(&s.KubeconfigPath, "kubeconfig-path", "", "Absolute path to the kubeconfig file.") 51 | pflag.StringVar(&s.MasterUrl, "master", "", "Master url.") 52 | pflag.IntVar(&s.QPS, "qps", 100, "qps of auto scaler.") 53 | pflag.IntVar(&s.Burst, "burst", 200, "burst of auto scaler.") 54 | } 55 | 56 | func (s *RunOptions) addElectionFlags() { 57 | pflag.StringVar(&s.ElectionName, "election-name", "general-podautoscaler", "election name.") 58 | pflag.StringVar(&s.ElectionNamespace, "election-namespace", "kube-system", "election namespace.") 59 | pflag.StringVar(&s.ElectionResourceLock, "election-resource-lock", "leases", "election resource type, support endoints, leases, configmaps and so on.") 60 | } 61 | 62 | // AddFlags adds flags related to GPAController for controller manager to the specified FlagSet. 63 | func (o *RunOptions) addGPAFlags() { 64 | if o == nil { 65 | return 66 | } 67 | 68 | pflag.DurationVar(&o.GeneralPodAutoscalerSyncPeriod.Duration, "general-pod-autoscaler-sync-period", o.GeneralPodAutoscalerSyncPeriod.Duration, "The period for syncing the number of pods in general pod autoscaler.") 69 | pflag.DurationVar(&o.GeneralPodAutoscalerUpscaleForbiddenWindow.Duration, "general-pod-autoscaler-upscale-delay", o.GeneralPodAutoscalerUpscaleForbiddenWindow.Duration, "The period since last upscale, before another upscale can be performed in general pod autoscaler.") 70 | pflag.DurationVar(&o.GeneralPodAutoscalerDownscaleStabilizationWindow.Duration, "general-pod-autoscaler-downscale-stabilization", o.GeneralPodAutoscalerDownscaleStabilizationWindow.Duration, "The period for which autoscaler will look backwards and not scale down below any recommendation it made during that period.") 71 | pflag.DurationVar(&o.GeneralPodAutoscalerDownscaleForbiddenWindow.Duration, "general-pod-autoscaler-downscale-delay", o.GeneralPodAutoscalerDownscaleForbiddenWindow.Duration, "The period since last downscale, before another downscale can be performed in general pod autoscaler.") 72 | pflag.Float64Var(&o.GeneralPodAutoscalerTolerance, "general-pod-autoscaler-tolerance", o.GeneralPodAutoscalerTolerance, "The minimum change (from 1.0) in the desired-to-actual metrics ratio for the general pod autoscaler to consider scaling.") 73 | pflag.BoolVar(&o.GeneralPodAutoscalerUseRESTClients, "general-pod-autoscaler-use-rest-clients", o.GeneralPodAutoscalerUseRESTClients, "If set to true, causes the general pod autoscaler controller to use REST clients through the kube-aggregator, instead of using the legacy metrics client through the API server proxy. This is required for custom metrics support in the general pod autoscaler.") 74 | pflag.DurationVar(&o.GeneralPodAutoscalerCPUInitializationPeriod.Duration, "general-pod-autoscaler-cpu-initialization-period", o.GeneralPodAutoscalerCPUInitializationPeriod.Duration, "The period after pod start when CPU samples might be skipped.") 75 | pflag.DurationVar(&o.GeneralPodAutoscalerInitialReadinessDelay.Duration, "general-pod-autoscaler-initial-readiness-delay", o.GeneralPodAutoscalerInitialReadinessDelay.Duration, "The period after pod start during which readiness changes will be treated as initial readiness.") 76 | } 77 | 78 | func (s *RunOptions) NewConfig() (*rest.Config, error) { 79 | var ( 80 | config *rest.Config 81 | err error 82 | ) 83 | config, err = rest.InClusterConfig() 84 | if err != nil { 85 | config, err = clientcmd.BuildConfigFromFlags(s.MasterUrl, s.KubeconfigPath) 86 | if err != nil { 87 | return nil, err 88 | } 89 | } 90 | config.Burst = s.Burst 91 | config.QPS = float32(s.QPS) 92 | return config, nil 93 | } 94 | -------------------------------------------------------------------------------- /cmd/gpa/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License.itations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "context" 19 | "flag" 20 | "fmt" 21 | "os" 22 | "time" 23 | 24 | "github.com/spf13/pflag" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/util/wait" 27 | "k8s.io/apiserver/pkg/server" 28 | "k8s.io/client-go/discovery" 29 | cacheddiscovery "k8s.io/client-go/discovery/cached" 30 | "k8s.io/client-go/dynamic" 31 | "k8s.io/client-go/informers" 32 | "k8s.io/client-go/kubernetes" 33 | "k8s.io/client-go/restmapper" 34 | "k8s.io/client-go/scale" 35 | "k8s.io/client-go/tools/leaderelection" 36 | "k8s.io/client-go/tools/leaderelection/resourcelock" 37 | componentbaseconfig "k8s.io/component-base/config" 38 | "k8s.io/klog" 39 | resourceclient "k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1beta1" 40 | "k8s.io/metrics/pkg/client/custom_metrics" 41 | "k8s.io/metrics/pkg/client/external_metrics" 42 | 43 | "github.com/ocgi/general-pod-autoscaler/cmd/gpa/app" 44 | "github.com/ocgi/general-pod-autoscaler/cmd/gpa/validator" 45 | autoscalingclient "github.com/ocgi/general-pod-autoscaler/pkg/client/clientset/versioned" 46 | autoscalinginformer "github.com/ocgi/general-pod-autoscaler/pkg/client/informers/externalversions" 47 | "github.com/ocgi/general-pod-autoscaler/pkg/metrics" 48 | "github.com/ocgi/general-pod-autoscaler/pkg/scaler" 49 | "github.com/ocgi/general-pod-autoscaler/pkg/version" 50 | ) 51 | 52 | const ( 53 | defaultLeaseDuration = 15 * time.Second 54 | defaultRenewDeadline = 10 * time.Second 55 | defaultRetryPeriod = 2 * time.Second 56 | ) 57 | 58 | func main() { 59 | runConfig := app.NewServerRunOptions() 60 | options := validator.NewServerRunOptions() 61 | pflag.CommandLine.AddGoFlagSet(flag.CommandLine) 62 | pflag.Parse() 63 | defer klog.Flush() 64 | version.Print() 65 | 66 | if options.ShowVersion { 67 | fmt.Println(os.Args[0], validator.Version) 68 | return 69 | } 70 | klog.Infof("Version: %s", validator.Version) 71 | 72 | klog.Infof("starting validator server.") 73 | if err := options.Validate(); err != nil { 74 | fmt.Fprintf(os.Stderr, "%v\n", err) 75 | os.Exit(1) 76 | } 77 | go func() { 78 | if err := validator.Run(options); err != nil { 79 | fmt.Fprintf(os.Stderr, "%v\n", err) 80 | os.Exit(1) 81 | } 82 | }() 83 | leaderElection := defaultLeaderElectionConfiguration() 84 | if len(runConfig.ElectionResourceLock) != 0 { 85 | leaderElection.ResourceLock = runConfig.ElectionResourceLock 86 | } 87 | 88 | kubeconfig, err := runConfig.NewConfig() 89 | if err != nil { 90 | klog.Fatal("Failed to build config") 91 | } 92 | 93 | stop := server.SetupSignalHandler() 94 | 95 | client := kubernetes.NewForConfigOrDie(kubeconfig) 96 | 97 | gpaClient := autoscalingclient.NewForConfigOrDie(kubeconfig) 98 | 99 | coreFactory := informers.NewSharedInformerFactory(client, runConfig.Resync) 100 | scalerFactory := autoscalinginformer.NewSharedInformerFactory(gpaClient, runConfig.Resync) 101 | 102 | cachedClient := cacheddiscovery.NewMemCacheClient(discovery.NewDiscoveryClientForConfigOrDie(kubeconfig)) 103 | restMapper := restmapper.NewDeferredDiscoveryRESTMapper(cachedClient) 104 | go wait.Until(func() { 105 | restMapper.Reset() 106 | }, 30*time.Second, stop) 107 | scaleKindResolver := scale.NewDiscoveryScaleKindResolver(client.Discovery()) 108 | scaleClient, err := scale.NewForConfig(kubeconfig, restMapper, dynamic.LegacyAPIPathResolverFunc, scaleKindResolver) 109 | if err != nil { 110 | klog.Fatal("Failed to build scale client %v", err) 111 | } 112 | 113 | apiVersionsGetter := custom_metrics.NewAvailableAPIsGetter(gpaClient.Discovery()) 114 | metricsClient := metrics.NewRESTMetricsClient( 115 | resourceclient.NewForConfigOrDie(kubeconfig), 116 | custom_metrics.NewForConfig(kubeconfig, restMapper, apiVersionsGetter), 117 | external_metrics.NewForConfigOrDie(kubeconfig), 118 | ) 119 | 120 | controller := scaler.NewGeneralController( 121 | client.CoreV1(), 122 | scaleClient, 123 | gpaClient.AutoscalingV1alpha1(), 124 | restMapper, 125 | metricsClient, 126 | scalerFactory.Autoscaling().V1alpha1().GeneralPodAutoscalers(), 127 | coreFactory.Core().V1().Pods(), 128 | runConfig.GeneralPodAutoscalerSyncPeriod.Duration, 129 | runConfig.GeneralPodAutoscalerDownscaleStabilizationWindow.Duration, 130 | runConfig.GeneralPodAutoscalerTolerance, 131 | runConfig.GeneralPodAutoscalerCPUInitializationPeriod.Duration, 132 | runConfig.GeneralPodAutoscalerInitialReadinessDelay.Duration, 133 | ) 134 | coreFactory.Start(stop) 135 | scalerFactory.Start(stop) 136 | ctx, cancel := context.WithCancel(context.TODO()) // TODO once Run() accepts a context, it should be used here 137 | defer cancel() 138 | 139 | go func() { 140 | select { 141 | case <-stop: 142 | cancel() 143 | case <-ctx.Done(): 144 | } 145 | }() 146 | 147 | run := func(ctx context.Context) { 148 | controller.Run(ctx.Done()) 149 | } 150 | 151 | id, err := os.Hostname() 152 | if err != nil { 153 | klog.Fatalf("Unable to get hostname: %v", err) 154 | } 155 | 156 | lock, err := resourcelock.New( 157 | leaderElection.ResourceLock, 158 | runConfig.ElectionNamespace, 159 | runConfig.ElectionName, 160 | client.CoreV1(), 161 | client.CoordinationV1(), 162 | resourcelock.ResourceLockConfig{ 163 | Identity: id, 164 | }, 165 | ) 166 | if err != nil { 167 | klog.Fatalf("Unable to create leader election lock: %v", err) 168 | } 169 | 170 | leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{ 171 | Lock: lock, 172 | LeaseDuration: leaderElection.LeaseDuration.Duration, 173 | RenewDeadline: leaderElection.RenewDeadline.Duration, 174 | RetryPeriod: leaderElection.RetryPeriod.Duration, 175 | Callbacks: leaderelection.LeaderCallbacks{ 176 | OnStartedLeading: func(ctx context.Context) { 177 | // Since we are committing a suicide after losing 178 | // mastership, we can safely ignore the argument. 179 | run(ctx) 180 | }, 181 | OnStoppedLeading: func() { 182 | klog.Fatalf("lost master") 183 | }, 184 | }, 185 | }) 186 | } 187 | 188 | func defaultLeaderElectionConfiguration() componentbaseconfig.LeaderElectionConfiguration { 189 | return componentbaseconfig.LeaderElectionConfiguration{ 190 | LeaderElect: false, 191 | LeaseDuration: metav1.Duration{Duration: defaultLeaseDuration}, 192 | RenewDeadline: metav1.Duration{Duration: defaultRenewDeadline}, 193 | RetryPeriod: metav1.Duration{Duration: defaultRetryPeriod}, 194 | ResourceLock: resourcelock.LeasesResourceLock, 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /cmd/gpa/validator/options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validator 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | 21 | "github.com/spf13/pflag" 22 | ) 23 | 24 | var ( 25 | Version = "unknown" 26 | ) 27 | 28 | type ServerRunOptions struct { 29 | Address string 30 | Port int 31 | TlsCA string 32 | TlsCert string 33 | TlsKey string 34 | IgnoreLabelKeys string 35 | ShowVersion bool 36 | SrcResourceName string 37 | DstResourceName string 38 | AllowDescheduleCount int 39 | } 40 | 41 | func NewServerRunOptions() *ServerRunOptions { 42 | options := &ServerRunOptions{} 43 | options.addFlags() 44 | return options 45 | } 46 | 47 | func (s *ServerRunOptions) addFlags() { 48 | pflag.StringVar(&s.Address, "address", "0.0.0.0", "The address of scheduler manager.") 49 | pflag.IntVar(&s.Port, "port", 8080, "The port of scheduler manager.") 50 | pflag.StringVar(&s.TlsCert, "tlscert", "", "Path to TLS certificate file") 51 | pflag.StringVar(&s.TlsKey, "tlskey", "", "Path to TLS key file") 52 | pflag.StringVar(&s.TlsCA, "CA", "", "Path to certificate file") 53 | pflag.BoolVar(&s.ShowVersion, "version", false, "Show version.") 54 | } 55 | 56 | func (s *ServerRunOptions) Validate() error { 57 | address := net.ParseIP(s.Address) 58 | if address.To4() == nil { 59 | return fmt.Errorf("%v is not a valid IP address\n", s.Address) 60 | } 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /cmd/gpa/validator/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validator 16 | 17 | import ( 18 | "context" 19 | "crypto/tls" 20 | "crypto/x509" 21 | "fmt" 22 | "io/ioutil" 23 | "net" 24 | "net/http" 25 | "net/http/pprof" 26 | "strconv" 27 | "time" 28 | 29 | "k8s.io/klog" 30 | 31 | "github.com/ocgi/general-pod-autoscaler/pkg/util" 32 | "github.com/ocgi/general-pod-autoscaler/pkg/validator" 33 | ) 34 | 35 | func Run(s *ServerRunOptions) error { 36 | stopCh := util.SetupSignalHandler() 37 | 38 | webHook := webhook.NewWebhookServer() 39 | 40 | // Start debug monitor. 41 | mux := http.NewServeMux() 42 | mux.HandleFunc("/mutate", webHook.Serve) 43 | mux.HandleFunc("/debug/pprof/", pprof.Index) 44 | mux.HandleFunc("/debug/pprof/profile", pprof.Profile) 45 | mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 46 | mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { 47 | fmt.Fprintf(w, "%s", "ok") 48 | }) 49 | 50 | server := &http.Server{ 51 | Addr: net.JoinHostPort(s.Address, strconv.Itoa(s.Port)), 52 | Handler: mux, 53 | ReadTimeout: 300 * time.Second, 54 | WriteTimeout: 300 * time.Second, 55 | } 56 | 57 | klog.V(1).Infof("listening on %v", server.Addr) 58 | if s.TlsCert != "" && s.TlsKey != "" { 59 | klog.V(1).Infof("using HTTPS service") 60 | tlsConfig, err := getTLSConfig(s) 61 | if err != nil { 62 | return err 63 | } 64 | server.TLSConfig = tlsConfig 65 | go func() { 66 | klog.Fatal(server.ListenAndServeTLS(s.TlsCert, s.TlsKey)) 67 | }() 68 | } else { 69 | go func() { 70 | klog.V(1).Infof("using HTTP service") 71 | klog.Fatal(server.ListenAndServe()) 72 | }() 73 | } 74 | 75 | select { 76 | case <-stopCh: 77 | klog.Info("http server received stop signal, waiting for all requests to finish") 78 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 79 | defer cancel() 80 | if err := server.Shutdown(ctx); err != nil { 81 | klog.Error(err) 82 | } 83 | } 84 | return nil 85 | } 86 | 87 | func getTLSConfig(s *ServerRunOptions) (*tls.Config, error) { 88 | tlsConfig := &tls.Config{ 89 | NextProtos: []string{"http/1.1"}, 90 | // Certificates: []tls.Certificate{cert}, 91 | // Avoid fallback on insecure SSL protocols 92 | MinVersion: tls.VersionTLS10, 93 | } 94 | if s.TlsCA != "" { 95 | certPool := x509.NewCertPool() 96 | file, err := ioutil.ReadFile(s.TlsCA) 97 | if err != nil { 98 | return nil, fmt.Errorf("Could not read CA certificate: %v", err) 99 | } 100 | certPool.AppendCertsFromPEM(file) 101 | tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert 102 | tlsConfig.ClientCAs = certPool 103 | } 104 | 105 | return tlsConfig, nil 106 | } 107 | -------------------------------------------------------------------------------- /docs/autoscaler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ocgi/general-pod-autoscaler/bbc2bc1ec168b9f0931015f4e9099d49e72156c4/docs/autoscaler.png -------------------------------------------------------------------------------- /docs/gs_scaledown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ocgi/general-pod-autoscaler/bbc2bc1ec168b9f0931015f4e9099d49e72156c4/docs/gs_scaledown.png -------------------------------------------------------------------------------- /examples/metric.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling.ocgi.dev/v1alpha1 2 | kind: GeneralPodAutoscaler 3 | metadata: 4 | name: pa-test1 5 | spec: 6 | maxReplicas: 10 7 | minReplicas: 2 8 | metric: 9 | metrics: 10 | - resource: 11 | name: cpu 12 | target: 13 | averageValue: 20 14 | type: AverageValue 15 | type: Resource 16 | - resource: 17 | name: memory 18 | target: 19 | averageValue: 50m 20 | type: AverageValue 21 | type: Resource 22 | scaleTargetRef: 23 | apiVersion: extensions/v1beta1 24 | kind: Deployment 25 | name: xzx -------------------------------------------------------------------------------- /examples/metric_custom.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling.ocgi.dev/v1alpha1 2 | kind: GeneralPodAutoscaler 3 | metadata: 4 | name: pa-custom 5 | namespace: default 6 | spec: 7 | maxReplicas: 8 8 | minReplicas: 1 9 | scaleTargetRef: 10 | apiVersion: game.scr.ied.com/v1 11 | kind: GameApp 12 | name: web-gameapp 13 | metric: 14 | metrics: 15 | - type: Pods 16 | pods: 17 | metric: 18 | name: memory_rss 19 | target: 20 | averageValue: 50m 21 | type: AverageValue 22 | - type: Pods 23 | pods: 24 | metric: 25 | name: cpu_usage 26 | target: 27 | averageValue: 200m 28 | type: AverageValue -------------------------------------------------------------------------------- /examples/time.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling.ocgi.dev/v1alpha1 2 | kind: GeneralPodAutoscaler 3 | metadata: 4 | name: pa-test1 5 | spec: 6 | maxReplicas: 8 7 | minReplicas: 2 8 | scaleTargetRef: 9 | apiVersion: extensions/v1beta1 10 | kind: Deployment 11 | name: xzx 12 | time: 13 | ranges: 14 | - desiredReplicas: 4 15 | schedule: '*/1 2-3 * * *' 16 | - desiredReplicas: 6 17 | schedule: '*/1 3-4 * * *' -------------------------------------------------------------------------------- /examples/webhook.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling.ocgi.dev/v1alpha1 2 | kind: GeneralPodAutoscaler 3 | metadata: 4 | name: pa-test1 5 | spec: 6 | maxReplicas: 8 7 | minReplicas: 2 8 | scaleTargetRef: 9 | apiVersion: carrier.ocgi.dev/v1alpha1 10 | kind: GameServerSet 11 | name: example 12 | webhook: 13 | service: 14 | namespace: kube-system 15 | name: demowebhook 16 | port: 8000 17 | path: scale 18 | parameters: 19 | buffer: "3" 20 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ocgi/general-pod-autoscaler 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/pkg/errors v0.9.1 7 | github.com/prometheus/client_golang v1.11.0 8 | github.com/robfig/cron v1.2.0 9 | github.com/spf13/pflag v1.0.5 10 | github.com/stretchr/testify v1.7.0 11 | k8s.io/api v0.23.5 12 | k8s.io/apimachinery v0.23.5 13 | k8s.io/apiserver v0.23.5 14 | k8s.io/client-go v0.23.5 15 | k8s.io/code-generator v0.23.5 16 | k8s.io/component-base v0.23.5 17 | k8s.io/heapster v1.2.0-beta.1 18 | k8s.io/klog v1.0.0 19 | k8s.io/metrics v0.23.5 20 | ) 21 | 22 | require ( 23 | github.com/NYTimes/gziphandler v1.1.1 // indirect 24 | github.com/PuerkitoBio/purell v1.1.1 // indirect 25 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 26 | github.com/beorn7/perks v1.0.1 // indirect 27 | github.com/blang/semver v3.5.1+incompatible // indirect 28 | github.com/cespare/xxhash/v2 v2.1.1 // indirect 29 | github.com/coreos/go-semver v0.3.0 // indirect 30 | github.com/coreos/go-systemd/v22 v22.3.2 // indirect 31 | github.com/davecgh/go-spew v1.1.1 // indirect 32 | github.com/emicklei/go-restful v2.9.5+incompatible // indirect 33 | github.com/evanphx/json-patch v4.12.0+incompatible // indirect 34 | github.com/felixge/httpsnoop v1.0.1 // indirect 35 | github.com/fsnotify/fsnotify v1.4.9 // indirect 36 | github.com/go-logr/logr v1.2.0 // indirect 37 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 38 | github.com/go-openapi/jsonreference v0.19.5 // indirect 39 | github.com/go-openapi/swag v0.19.14 // indirect 40 | github.com/gogo/protobuf v1.3.2 // indirect 41 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 42 | github.com/golang/protobuf v1.5.2 // indirect 43 | github.com/google/go-cmp v0.5.5 // indirect 44 | github.com/google/gofuzz v1.1.0 // indirect 45 | github.com/google/uuid v1.1.2 // indirect 46 | github.com/googleapis/gnostic v0.5.5 // indirect 47 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect 48 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect 49 | github.com/imdario/mergo v0.3.9 // indirect 50 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 51 | github.com/josharian/intern v1.0.0 // indirect 52 | github.com/json-iterator/go v1.1.12 // indirect 53 | github.com/mailru/easyjson v0.7.6 // indirect 54 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 55 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 56 | github.com/modern-go/reflect2 v1.0.2 // indirect 57 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 58 | github.com/pmezard/go-difflib v1.0.0 // indirect 59 | github.com/prometheus/client_model v0.2.0 // indirect 60 | github.com/prometheus/common v0.28.0 // indirect 61 | github.com/prometheus/procfs v0.6.0 // indirect 62 | github.com/spf13/cobra v1.2.1 // indirect 63 | go.etcd.io/etcd/api/v3 v3.5.0 // indirect 64 | go.etcd.io/etcd/client/pkg/v3 v3.5.0 // indirect 65 | go.etcd.io/etcd/client/v3 v3.5.0 // indirect 66 | go.opentelemetry.io/contrib v0.20.0 // indirect 67 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 // indirect 68 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0 // indirect 69 | go.opentelemetry.io/otel v0.20.0 // indirect 70 | go.opentelemetry.io/otel/exporters/otlp v0.20.0 // indirect 71 | go.opentelemetry.io/otel/metric v0.20.0 // indirect 72 | go.opentelemetry.io/otel/sdk v0.20.0 // indirect 73 | go.opentelemetry.io/otel/sdk/export/metric v0.20.0 // indirect 74 | go.opentelemetry.io/otel/sdk/metric v0.20.0 // indirect 75 | go.opentelemetry.io/otel/trace v0.20.0 // indirect 76 | go.opentelemetry.io/proto/otlp v0.7.0 // indirect 77 | go.uber.org/atomic v1.7.0 // indirect 78 | go.uber.org/multierr v1.6.0 // indirect 79 | go.uber.org/zap v1.19.0 // indirect 80 | golang.org/x/mod v0.4.2 // indirect 81 | golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect 82 | golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect 83 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect 84 | golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e // indirect 85 | golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect 86 | golang.org/x/text v0.3.7 // indirect 87 | golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect 88 | golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff // indirect 89 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 90 | google.golang.org/appengine v1.6.7 // indirect 91 | google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2 // indirect 92 | google.golang.org/grpc v1.40.0 // indirect 93 | google.golang.org/protobuf v1.27.1 // indirect 94 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect 95 | gopkg.in/inf.v0 v0.9.1 // indirect 96 | gopkg.in/yaml.v2 v2.4.0 // indirect 97 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 98 | k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c // indirect 99 | k8s.io/klog/v2 v2.30.0 // indirect 100 | k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect 101 | k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect 102 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30 // indirect 103 | sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect 104 | sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect 105 | sigs.k8s.io/yaml v1.2.0 // indirect 106 | ) 107 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /hack/tools.go: -------------------------------------------------------------------------------- 1 | // +build tools 2 | 3 | // Package tools imports things required by build scripts, to force `go mod` to see them as dependencies 4 | package tools 5 | 6 | import ( 7 | // Import code-generator to use in build tools 8 | _ "k8s.io/code-generator" 9 | ) -------------------------------------------------------------------------------- /hack/update-codegen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | SCRIPT_ROOT=$(dirname ${BASH_SOURCE})/.. 22 | CODEGEN_PKG=${CODEGEN_PKG:-$(cd ${SCRIPT_ROOT}; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../../code-generator)} 23 | 24 | # generate the code with: 25 | # --output-base because this script should also be able to run inside the vendor dir of 26 | # k8s.io/kubernetes. The output-base is needed for the generators to output into the vendor dir 27 | # instead of the $GOPATH directly. For normal projects this can be dropped. 28 | 29 | bash ${CODEGEN_PKG}/generate-groups.sh all \ 30 | github.com/ocgi/general-pod-autoscaler/pkg/client github.com/ocgi/general-pod-autoscaler/pkg/apis \ 31 | "autoscaling:v1alpha1" \ 32 | --go-header-file ${SCRIPT_ROOT}/hack/boilerplate.go.txt 33 | 34 | bash ${CODEGEN_PKG}/generate-groups.sh "deepcopy" \ 35 | github.com/ocgi/general-pod-autoscaler/pkg/apis github.com/ocgi/general-pod-autoscaler/pkg/apis \ 36 | "config:v1alpha1" \ 37 | --go-header-file ${SCRIPT_ROOT}/hack/boilerplate.go.txt 38 | -------------------------------------------------------------------------------- /hack/verify-codegen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2017 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | SCRIPT_ROOT=$(dirname "${BASH_SOURCE}")/.. 22 | 23 | DIFFROOT="${SCRIPT_ROOT}/pkg" 24 | TMP_DIFFROOT="${SCRIPT_ROOT}/_tmp/pkg" 25 | _tmp="${SCRIPT_ROOT}/_tmp" 26 | 27 | cleanup() { 28 | rm -rf "${_tmp}" 29 | } 30 | trap "cleanup" EXIT SIGINT 31 | 32 | cleanup 33 | 34 | mkdir -p "${TMP_DIFFROOT}" 35 | cp -a "${DIFFROOT}"/* "${TMP_DIFFROOT}" 36 | 37 | "${SCRIPT_ROOT}/hack/update-codegen.sh" 38 | echo "diffing ${DIFFROOT} against freshly generated codegen" 39 | ret=0 40 | diff -Naupr "${DIFFROOT}" "${TMP_DIFFROOT}" || ret=$? 41 | cp -a "${TMP_DIFFROOT}"/* "${DIFFROOT}" 42 | if [[ $ret -eq 0 ]] 43 | then 44 | echo "${DIFFROOT} up to date." 45 | else 46 | echo "${DIFFROOT} is out of date. Please run hack/update-codegen.sh" 47 | exit 1 48 | fi 49 | -------------------------------------------------------------------------------- /manifeasts/crd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: generalpodautoscalers.autoscaling.ocgi.dev 5 | spec: 6 | additionalPrinterColumns: 7 | - JSONPath: .spec.minReplicas 8 | name: MinReplicas 9 | type: integer 10 | - JSONPath: .spec.maxReplicas 11 | name: MaxReplicas 12 | type: integer 13 | - JSONPath: .status.desiredReplicas 14 | name: Desired 15 | type: integer 16 | - JSONPath: .status.currentReplicas 17 | name: Current 18 | type: integer 19 | - JSONPath: .spec.scaleTargetRef.kind 20 | name: TargetKind 21 | type: string 22 | - JSONPath: .spec.scaleTargetRef.name 23 | name: TargetName 24 | type: string 25 | group: autoscaling.ocgi.dev 26 | names: 27 | kind: GeneralPodAutoscaler 28 | listKind: GeneralPodAutoscalerList 29 | plural: generalpodautoscalers 30 | shortNames: 31 | - pa 32 | - gpa 33 | singular: generalpodautoscaler 34 | scope: Namespaced 35 | subresources: 36 | status: {} 37 | version: v1alpha1 38 | versions: 39 | - name: v1alpha1 40 | served: true 41 | storage: true 42 | -------------------------------------------------------------------------------- /manifeasts/gpa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: gpa 5 | namespace: kube-system 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1 8 | kind: ClusterRole 9 | metadata: 10 | name: gpa 11 | rules: 12 | - apiGroups: 13 | - autoscaling.ocgi.dev 14 | resources: 15 | - generalpodautoscalers 16 | verbs: 17 | - get 18 | - list 19 | - watch 20 | - apiGroups: 21 | - autoscaling.ocgi.dev 22 | resources: 23 | - generalpodautoscalers/status 24 | verbs: 25 | - update 26 | - apiGroups: 27 | - '*' 28 | resources: 29 | - '*/scale' 30 | verbs: 31 | - get 32 | - update 33 | - apiGroups: 34 | - "" 35 | resources: 36 | - pods 37 | verbs: 38 | - list 39 | - watch 40 | - apiGroups: 41 | - "" 42 | resourceNames: 43 | - 'http:heapster:' 44 | - 'https:heapster:' 45 | resources: 46 | - services/proxy 47 | verbs: 48 | - get 49 | - apiGroups: 50 | - metrics.k8s.io 51 | resources: 52 | - pods 53 | verbs: 54 | - list 55 | - apiGroups: 56 | - custom.metrics.k8s.io 57 | resources: 58 | - '*' 59 | verbs: 60 | - get 61 | - list 62 | - apiGroups: 63 | - external.metrics.k8s.io 64 | resources: 65 | - '*' 66 | verbs: 67 | - get 68 | - list 69 | - apiGroups: 70 | - "" 71 | resources: 72 | - events 73 | verbs: 74 | - create 75 | - patch 76 | - update 77 | - apiGroups: 78 | - "" 79 | resources: 80 | - endpoints 81 | verbs: 82 | - "*" 83 | - apiGroups: 84 | - coordination.k8s.io 85 | resources: 86 | - leases 87 | verbs: 88 | - "*" 89 | 90 | --- 91 | apiVersion: rbac.authorization.k8s.io/v1 92 | kind: ClusterRoleBinding 93 | metadata: 94 | name: gpa 95 | roleRef: 96 | apiGroup: rbac.authorization.k8s.io 97 | kind: ClusterRole 98 | name: gpa 99 | subjects: 100 | - kind: ServiceAccount 101 | name: gpa 102 | namespace: kube-system 103 | --- 104 | apiVersion: v1 105 | data: 106 | cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURTVENDQWpHZ0F3SUJBZ0lKQU1wdmNlV1JTeFlxTUEwR0NTcUdTSWIzRFFFQkN3VUFNQmN4RlRBVEJnTlYKQkFNTURIWmhiR2xrWVhSdmNsOWpZVEFnRncweU1UQTFNVFV4TXpNNE5EWmFHQTh5TWprMU1ESXlPREV6TXpnMApObG93S0RFbU1DUUdBMVVFQXd3ZFozQmhMWFpoYkdsa1lYUnZjaTVyZFdKbExYTjVjM1JsYlM1emRtTXdnZ0VpCk1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRQzloSG9ZRU1GbUFpNDhaalZuRHQvaG9xbnMKUXpTZEc3VUZoWk0wcmlneVRwSWVoU21PdUJKUHgvMXY2cjQwVXhqTTB3L1dQL2kzN1hRL3g3TDJSQkNaSGZwMwpOZHJ1alZaZkx3b2ZoZU80eG5BU2tHSlhFSGtWNllmakdRNUoxMHRSZVBsWjdKazBCelcwSTF1UnVEZERoNDAwClVvbUhvSThBVDFQOXI3a0I5dDBpaHdHMVZxNnVmdFRJUFNwandjNWtWMnRMT2VDLzR5eUQ5VjZwKytQMFVycWsKOFhLVE9mSUFzY3FFSjhSNWY1a1lhQVozTG0vem5wSk9ZYThNVDQ0T2twRTB5bnJvMDNLeXJhNGl2YTdKdy9FMQozZGtBL2s0YWZnTFYzNGNUdWhjRHhucmNpQUI4WCtNV2xGZW14MDlzeG9PR3dXaDJmZVpxTEY3bS9GbExBZ01CCkFBR2pnWVF3Z1lFd0NRWURWUjBUQkFJd0FEQUxCZ05WSFE4RUJBTUNCZUF3RXdZRFZSMGxCQXd3Q2dZSUt3WUIKQlFVSEF3RXdVZ1lEVlIwUkJFc3dTWUlOWjNCaExYWmhiR2xrWVhSdmNvSVpaM0JoTFhaaGJHbGtZWFJ2Y2k1cgpkV0psTFhONWMzUmxiWUlkWjNCaExYWmhiR2xrWVhSdmNpNXJkV0psTFhONWMzUmxiUzV6ZG1Nd0RRWUpLb1pJCmh2Y05BUUVMQlFBRGdnRUJBSWN3T1A3MGx1Tjh3cW92K0NCdEt6ZmRPbkp1bStqSGpFNFIzSlljMVdQWkpKWG4KTWhsZFFIOStWNmNlblJrNWYzOHhSL2ZEdXhTODBqNUdzSzFhWUs5cU5NNGJhSUNYa1FQaVZ6WWd0NitOaWNMYQp3eWJuS0tNS0JncXhzeGZlMDdObTY2cFFRV2xxSlV4TEhSNWJ0WFpTVkk0amg5cjBjTmpBdDFVRXZNUGs3ZW9tCkIyQy9OSVIwaWVIVTMwaTZoU0ZhZW01NHp2NVVSVDNMeklNSHI3QTRwSi80SVVxRVlpWmJlSnEwdHZZaEVIR3QKTVFIcjdLYWZnUWhQd3hnSEFpUW95aEtybURxWUJyWWphWHNxUjNpaklrR1N5WVBiSXNVUnVkdjBHai9tQXRybApyVDNoSytGWjI5K0MvbDgrbTdVTEZ0VWdvMjFZUjNWMDlRWnRVYW89Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K 107 | key.pem: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBdllSNkdCREJaZ0l1UEdZMVp3N2Y0YUtwN0VNMG5SdTFCWVdUTks0b01rNlNIb1VwCmpyZ1NUOGY5YitxK05GTVl6Tk1QMWovNHQrMTBQOGV5OWtRUW1SMzZkelhhN28xV1h5OEtINFhqdU1ad0VwQmkKVnhCNUZlbUg0eGtPU2RkTFVYajVXZXlaTkFjMXRDTmJrYmczUTRlTk5GS0poNkNQQUU5VC9hKzVBZmJkSW9jQgp0VmF1cm43VXlEMHFZOEhPWkZkclN6bmd2K01zZy9WZXFmdmo5Rks2cFBGeWt6bnlBTEhLaENmRWVYK1pHR2dHCmR5NXY4NTZTVG1HdkRFK09EcEtSTk1wNjZOTnlzcTJ1SXIydXljUHhOZDNaQVA1T0duNEMxZCtIRTdvWEE4WjYKM0lnQWZGL2pGcFJYcHNkUGJNYURoc0ZvZG4zbWFpeGU1dnhaU3dJREFRQUJBb0lCQURvcU9KYStQYjBqR2pHcgpaT1IyYWp3RUFvYmlzWGt1a2NaYUVxM3ViYVBTL1lHa2VQYTJnOWc4bHdvTVQ4Z2JZTmNzU0FZSHFEdzdBcHk0Cm03SjlmV0toRXNnWG5WUHR5UDU1bjIvQlZmelYybnU0R0phRUZjV2w0UERRTmRSdjRVdm1CeEdVVGdqcjFZMnQKOUdLcTJUcytsUlhqNXhYYkwwaDNFRjRoMm5SQldpZmh2akRkMXdWY3FYQXZkakJSeUxhQmZMZVRrZWlxTVkzegpwWlRYMEpMNGFVMzQ4UmExaGRXMy93b21PSEVRbENkYVdIempQVUVXa2dWa0IxS1BnWHVDbExqcEdDMFY2MzI1Cmd2RVk5UzYwb2ZtK29hRWdTaURtSGdUMXcyRk43ZC9weVhvOTRYcHdGaG9wZk45VWRPaENsNjhVNG8zYTltdm0KZWRjdHNKRUNnWUVBOFE0c0JablF4UHVMYmdwOFZJaFhNWGRTaHJNOVNUOUo1dG15NVlUQk9rc2sxYllqYXpQYwpGRGY5ZmFXbDNMeCttczVjZXVoZ2dveThMMWxnMzdxVWVJbGdDeVdFNkcxQm53d2pybUo5L1lCLzZCaTNrZVlFCnljR1VJbXBtQ1BCOE50MUpXTHEzK0xSV0lkWnB6aDY4Z2pkZmhDSllwUVdwcFdkN3FjUk5MbmNDZ1lFQXlVUlcKMWJBMGJ1T21hbDdZamZtb1FHdEYzUjh6RW1WSXgrcCtHRFQzek5SZEhCQXkwUlRuUFZOdmlYTXk3OFFFTmVpNQpnWW5SbDA4cG51ZGlXQTQydmxNR1ljUjJUZlN1MEFQWUQ0ejc0SWpueDBIWkI3TkZ2UXUwcWdqUWFybzVPbEJLClFiaWtIODZJM3F4aFZCZFdIcUUxMGNmN1NWbCtQUURMa05kWC9NMENnWUJ2a2N5a1J2NE92aEpadXpheWRXeE4KODlsWmlRbzdwYlJSeWI3NFprcDg3Wnk5NFpCaEpONmU2UTlGb1c2MFRQWVlOcDB0TWZMQldkd2IyalZ4TjgxUwpRTnNZTGFSblkzSTA2QXgzd1JScFNabHAxazdZazJyeDZzdmlaZmFWK3FDelVCYUxuOWhZbnpjaWNEZ0djOGx5ClI2akk0cEMreUhFdG9ramxXWHNUbndLQmdRQ3FMY1NXanBUbkprb3lLdHhFdjlKS0ZlcE8yL0NkSDFMNDV3ZVQKaEVYNnhaMnMrdTFjNktYVnJ0VGlqYXVHSEdITEMycS9Zejc3QjQ2VHMrWHR1cWNES3NSOHFsZTE5cXhSYXM2bgo1M2VhRldpVmpOSHJ0dVBDdzBFUmZMNXRYK1MvcjkvNXpodklZU1JOMDhmQUNMTmkxc0RoRG8wbEtpeVpmRENqCjJkR21DUUtCZ1FDTk83ZDJkdWhVUzRtL1gxNWdkcFU5TlpwZXB1bzZtNGFRR0NIUnVnL01vKzdIVDA1NWVTR0MKUUswRFJpOCt1YkFQSXBQMlFOMU9WSkI1MEZSL1R5RTF3cldzRXZ0dHBaVFpIaWNDMnJUZVpsWWJUWGxtUEF5dwpOclJYUTFLKzNFNkVJV1BEWDVxZVlLcjFKM1pWMm5xSXFQRU9pSE9oREtWSHF4REhzbjEwenc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= 108 | kind: Secret 109 | metadata: 110 | name: gpa-secret 111 | namespace: kube-system 112 | type: Opaque 113 | 114 | --- 115 | apiVersion: apps/v1 116 | kind: Deployment 117 | metadata: 118 | name: gpa 119 | namespace: kube-system 120 | spec: 121 | replicas: 1 122 | selector: 123 | matchLabels: 124 | app: gpa-service 125 | template: 126 | metadata: 127 | labels: 128 | app: gpa-service 129 | spec: 130 | serviceAccountName: gpa 131 | containers: 132 | - args: 133 | - --tlscert=/root/cert.pem 134 | - --tlskey=/root/key.pem 135 | - --v=6 136 | - --port=443 137 | image: ocgi/gpa:latest 138 | imagePullPolicy: Always 139 | name: gpa 140 | ports: 141 | - containerPort: 8080 142 | volumeMounts: 143 | - mountPath: /root 144 | name: gpasecret 145 | volumes: 146 | - name: gpasecret 147 | secret: 148 | secretName: gpa-secret 149 | items: 150 | - key: key.pem 151 | path: key.pem 152 | - key: cert.pem 153 | path: cert.pem 154 | 155 | --- 156 | apiVersion: v1 157 | kind: Service 158 | metadata: 159 | name: gpa-validator 160 | namespace: kube-system 161 | spec: 162 | selector: 163 | app: gpa-service 164 | ports: 165 | - port: 443 166 | targetPort: 443 167 | -------------------------------------------------------------------------------- /manifeasts/validatorconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: admissionregistration.k8s.io/v1beta1 2 | kind: MutatingWebhookConfiguration 3 | metadata: 4 | name: gpa-validator 5 | webhooks: 6 | - admissionReviewVersions: 7 | - v1beta1 8 | clientConfig: 9 | caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURBekNDQWV1Z0F3SUJBZ0lKQU15M2dxY05QNmo4TUEwR0NTcUdTSWIzRFFFQkN3VUFNQmN4RlRBVEJnTlYKQkFNTURIWmhiR2xrWVhSdmNsOWpZVEFnRncweU1UQTFNVFV4TXpNNE5EWmFHQTh5TWprMU1ESXlPREV6TXpnMApObG93RnpFVk1CTUdBMVVFQXd3TWRtRnNhV1JoZEc5eVgyTmhNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DCkFROEFNSUlCQ2dLQ0FRRUFxdm91RGppWFFKc3V6VUk4VlBHQUlYQXl0YXhMeGlLQVpzQVE2SUx1dlRwTUNnaTAKWTQwdktGcGdBU29zTTBYQUg4VWlHeWRXYldQd21uMi9qc2tKMjk5VGhPdnN4VzBHYU1BNHpqd3NHNUU3ZUZyUQp0RGlWZVd5S1k0MWV0YVpFRWpPNjB5TVdTTTNjL2hyeDJkdm4yblZ3WGJ5c3FXV1ZxYjgvM2NkRU5OTTVmUXNRClp0WEQyNG1qQUMya2d2MjVVSHNranNoampoQ3NDeXlIWVRqNGl2dkZFUG9hSTN6M2ZKSng2NmgyazB4WXhGQUwKc0VkU1ptRFJBZHNheTBacExEbjZ1ZU42NzVIR0dxMGx2SmVRRGR6Y2l2RnNYM25pL3owU1ExdW5MWDMvMWl6bApWOWNXaXlOOHlXb1BjbUQyejVHUjJKMnpoL28vTzNaaFNsNXQwUUlEQVFBQm8xQXdUakFkQmdOVkhRNEVGZ1FVCjZZemhjR0U5dkZJN0FyaHdDTUNaQXMydFBFOHdId1lEVlIwakJCZ3dGb0FVNll6aGNHRTl2Rkk3QXJod0NNQ1oKQXMydFBFOHdEQVlEVlIwVEJBVXdBd0VCL3pBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQWxPd2RHL0ZGbmN0UwpyWUhVWnhyZUV4Nm1BakdxRW9ZaE92WUpySU5Ec29qRzZsL2Z5cnRmOG5POXhEb2J0K0x1eW5rdFp0WVlqQ1F4CjJDV0VvbDIvN1dyczRjUEpXV0NQQUp6NGNyYjRVclNGWDhUTmJWZllVWDYxNHMrVUtEZDcvZmhQa0toVUFpaGwKeFQ3K3hJU3QyQi9MbnYwbGRNV0RzTlcwSTNYUFFqZURnM2lFSGVONjQxbnNmTjVtR1dRblc2RDJaek9lL3dmVgpVSzRVS3ZlSFFsVlNjZjFSbzVSOFQ1TkMwQVNMUW1DSUVDT2JSaDFBSjA0Y3l0MGdhYU5CNUNQaHlXVDdoTE9SCmxhVEdsWnVTOGt2WVd5ZTM1UEM2N3Z0RWRYRXowcExSdTNpVHBoMEkrbGZucWRPVDZkQjBRa0VyT1ZlNFZ2UHYKaTF4SGVXa1dzQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K 10 | service: 11 | namespace: kube-system 12 | name: gpa-validator 13 | path: /mutate 14 | failurePolicy: Ignore 15 | name: gpa-validator.autoscaling.ocgi.dev 16 | namespaceSelector: 17 | matchExpressions: 18 | - key: ns 19 | operator: NotIn 20 | values: 21 | - kube-system 22 | rules: 23 | - apiGroups: 24 | - "autoscaling.ocgi.dev" 25 | apiVersions: 26 | - v1alpha1 27 | operations: 28 | - CREATE 29 | - UPDATE 30 | resources: 31 | - '*' 32 | scope: '*' 33 | sideEffects: None 34 | timeoutSeconds: 10 -------------------------------------------------------------------------------- /pkg/apis/autoscaling/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 THL A29 Limited, a Tencent company. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package autoscaling 18 | 19 | // GroupName is the group name used in this package 20 | const ( 21 | GroupName = "autoscaling.ocgi.dev" 22 | ) 23 | -------------------------------------------------------------------------------- /pkg/apis/autoscaling/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 THL A29 Limited, a Tencent company. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // +k8s:deepcopy-gen=package 18 | // +groupName=autoscaling.ocgi.dev 19 | 20 | // Package v1alpha1 is the v1alpha1 version of the API. 21 | package v1alpha1 // import "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling/v1alpha1" 22 | -------------------------------------------------------------------------------- /pkg/apis/autoscaling/v1alpha1/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 THL A29 Limited, a Tencent company. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | "k8s.io/apimachinery/pkg/runtime" 22 | "k8s.io/apimachinery/pkg/runtime/schema" 23 | 24 | "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling" 25 | ) 26 | 27 | // SchemeGroupVersion is group version used to register these objects 28 | var SchemeGroupVersion = schema.GroupVersion{Group: autoscaling.GroupName, Version: "v1alpha1"} 29 | 30 | // Kind takes an unqualified kind and returns back a Group qualified GroupKind 31 | func Kind(kind string) schema.GroupKind { 32 | return SchemeGroupVersion.WithKind(kind).GroupKind() 33 | } 34 | 35 | // Resource takes an unqualified resource and returns a Group qualified GroupResource 36 | func Resource(resource string) schema.GroupResource { 37 | return SchemeGroupVersion.WithResource(resource).GroupResource() 38 | } 39 | 40 | var ( 41 | // SchemeBuilder initializes a scheme builder 42 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) 43 | // AddToScheme is a global function that registers this API group & version to a scheme 44 | AddToScheme = SchemeBuilder.AddToScheme 45 | ) 46 | 47 | // Adds the list of known types to Scheme. 48 | func addKnownTypes(scheme *runtime.Scheme) error { 49 | scheme.AddKnownTypes(SchemeGroupVersion, 50 | &GeneralPodAutoscaler{}, 51 | &GeneralPodAutoscalerList{}, 52 | ) 53 | metav1.AddToGroupVersion(scheme, SchemeGroupVersion) 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/apis/config/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 THL A29 Limited, a Tencent company. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package autoscaling 18 | 19 | // GroupName is the group name used in this package 20 | const ( 21 | GroupName = "autoscalingconfig.ocgi.dev" 22 | ) 23 | -------------------------------------------------------------------------------- /pkg/apis/config/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // +k8s:deepcopy-gen=package 18 | 19 | package v1alpha1 // import "github.com/ocgi/general-pod-autoscaler/pkg/apis/config/v1alpha1" 20 | -------------------------------------------------------------------------------- /pkg/apis/config/v1alpha1/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 THL A29 Limited, a Tencent company. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | "k8s.io/apimachinery/pkg/runtime" 22 | "k8s.io/apimachinery/pkg/runtime/schema" 23 | 24 | "github.com/ocgi/general-pod-autoscaler/pkg/apis/config" 25 | ) 26 | 27 | // SchemeGroupVersion is group version used to register these objects 28 | var SchemeGroupVersion = schema.GroupVersion{Group: autoscaling.GroupName, Version: "v1alpha1"} 29 | 30 | // Kind takes an unqualified kind and returns back a Group qualified GroupKind 31 | func Kind(kind string) schema.GroupKind { 32 | return SchemeGroupVersion.WithKind(kind).GroupKind() 33 | } 34 | 35 | // Resource takes an unqualified resource and returns a Group qualified GroupResource 36 | func Resource(resource string) schema.GroupResource { 37 | return SchemeGroupVersion.WithResource(resource).GroupResource() 38 | } 39 | 40 | var ( 41 | // SchemeBuilder initializes a scheme builder 42 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) 43 | // AddToScheme is a global function that registers this API group & version to a scheme 44 | AddToScheme = SchemeBuilder.AddToScheme 45 | ) 46 | 47 | // Adds the list of known types to Scheme. 48 | func addKnownTypes(scheme *runtime.Scheme) error { 49 | scheme.AddKnownTypes(SchemeGroupVersion, 50 | &GPAControllerConfiguration{}, 51 | ) 52 | metav1.AddToGroupVersion(scheme, SchemeGroupVersion) 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /pkg/apis/config/v1alpha1/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 24 | 25 | // GPAControllerConfiguration contains elements describing GPAController. 26 | type GPAControllerConfiguration struct { 27 | metav1.TypeMeta `json:",inline"` 28 | // generalPodAutoscalerSyncPeriod is the period for syncing the number of 29 | // pods in general pod autoscaler. 30 | GeneralPodAutoscalerSyncPeriod metav1.Duration 31 | // generalPodAutoscalerUpscaleForbiddenWindow is a period after which next upscale allowed. 32 | GeneralPodAutoscalerUpscaleForbiddenWindow metav1.Duration 33 | // generalPodAutoscalerDownscaleForbiddenWindow is a period after which next downscale allowed. 34 | GeneralPodAutoscalerDownscaleForbiddenWindow metav1.Duration 35 | // GeneralPodAutoscalerDowncaleStabilizationWindow is a period for which autoscaler will look 36 | // backwards and not scale down below any recommendation it made during that period. 37 | GeneralPodAutoscalerDownscaleStabilizationWindow metav1.Duration 38 | // generalPodAutoscalerTolerance is the tolerance for when 39 | // resource usage suggests upscaling/downscaling 40 | GeneralPodAutoscalerTolerance float64 41 | // GeneralPodAutoscalerUseRESTClients causes the GPA controller to use REST clients 42 | // through the kube-aggregator when enabled, instead of using the legacy metrics client 43 | // through the API server proxy. 44 | GeneralPodAutoscalerUseRESTClients bool 45 | // GeneralPodAutoscalerCPUInitializationPeriod is the period after pod start when CPU samples 46 | // might be skipped. 47 | GeneralPodAutoscalerCPUInitializationPeriod metav1.Duration 48 | // GeneralPodAutoscalerInitialReadinessDelay is period after pod start during which readiness 49 | // changes are treated as readiness being set for the first time. The only effect of this is that 50 | // GPA will disregard CPU samples from unready pods that had last readiness change during that 51 | // period. 52 | GeneralPodAutoscalerInitialReadinessDelay metav1.Duration 53 | } 54 | -------------------------------------------------------------------------------- /pkg/apis/config/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | // Copyright 2021 The OCGI Authors. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | // Code generated by deepcopy-gen. DO NOT EDIT. 19 | 20 | package v1alpha1 21 | 22 | import ( 23 | runtime "k8s.io/apimachinery/pkg/runtime" 24 | ) 25 | 26 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 27 | func (in *GPAControllerConfiguration) DeepCopyInto(out *GPAControllerConfiguration) { 28 | *out = *in 29 | out.TypeMeta = in.TypeMeta 30 | out.GeneralPodAutoscalerSyncPeriod = in.GeneralPodAutoscalerSyncPeriod 31 | out.GeneralPodAutoscalerUpscaleForbiddenWindow = in.GeneralPodAutoscalerUpscaleForbiddenWindow 32 | out.GeneralPodAutoscalerDownscaleForbiddenWindow = in.GeneralPodAutoscalerDownscaleForbiddenWindow 33 | out.GeneralPodAutoscalerDownscaleStabilizationWindow = in.GeneralPodAutoscalerDownscaleStabilizationWindow 34 | out.GeneralPodAutoscalerCPUInitializationPeriod = in.GeneralPodAutoscalerCPUInitializationPeriod 35 | out.GeneralPodAutoscalerInitialReadinessDelay = in.GeneralPodAutoscalerInitialReadinessDelay 36 | return 37 | } 38 | 39 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GPAControllerConfiguration. 40 | func (in *GPAControllerConfiguration) DeepCopy() *GPAControllerConfiguration { 41 | if in == nil { 42 | return nil 43 | } 44 | out := new(GPAControllerConfiguration) 45 | in.DeepCopyInto(out) 46 | return out 47 | } 48 | 49 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 50 | func (in *GPAControllerConfiguration) DeepCopyObject() runtime.Object { 51 | if c := in.DeepCopy(); c != nil { 52 | return c 53 | } 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/clientset.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | package versioned 18 | 19 | import ( 20 | "fmt" 21 | "net/http" 22 | 23 | autoscalingv1alpha1 "github.com/ocgi/general-pod-autoscaler/pkg/client/clientset/versioned/typed/autoscaling/v1alpha1" 24 | discovery "k8s.io/client-go/discovery" 25 | rest "k8s.io/client-go/rest" 26 | flowcontrol "k8s.io/client-go/util/flowcontrol" 27 | ) 28 | 29 | type Interface interface { 30 | Discovery() discovery.DiscoveryInterface 31 | AutoscalingV1alpha1() autoscalingv1alpha1.AutoscalingV1alpha1Interface 32 | } 33 | 34 | // Clientset contains the clients for groups. Each group has exactly one 35 | // version included in a Clientset. 36 | type Clientset struct { 37 | *discovery.DiscoveryClient 38 | autoscalingV1alpha1 *autoscalingv1alpha1.AutoscalingV1alpha1Client 39 | } 40 | 41 | // AutoscalingV1alpha1 retrieves the AutoscalingV1alpha1Client 42 | func (c *Clientset) AutoscalingV1alpha1() autoscalingv1alpha1.AutoscalingV1alpha1Interface { 43 | return c.autoscalingV1alpha1 44 | } 45 | 46 | // Discovery retrieves the DiscoveryClient 47 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 48 | if c == nil { 49 | return nil 50 | } 51 | return c.DiscoveryClient 52 | } 53 | 54 | // NewForConfig creates a new Clientset for the given config. 55 | // If config's RateLimiter is not set and QPS and Burst are acceptable, 56 | // NewForConfig will generate a rate-limiter in configShallowCopy. 57 | // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), 58 | // where httpClient was generated with rest.HTTPClientFor(c). 59 | func NewForConfig(c *rest.Config) (*Clientset, error) { 60 | configShallowCopy := *c 61 | 62 | // share the transport between all clients 63 | httpClient, err := rest.HTTPClientFor(&configShallowCopy) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | return NewForConfigAndClient(&configShallowCopy, httpClient) 69 | } 70 | 71 | // NewForConfigAndClient creates a new Clientset for the given config and http client. 72 | // Note the http client provided takes precedence over the configured transport values. 73 | // If config's RateLimiter is not set and QPS and Burst are acceptable, 74 | // NewForConfigAndClient will generate a rate-limiter in configShallowCopy. 75 | func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) { 76 | configShallowCopy := *c 77 | if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { 78 | if configShallowCopy.Burst <= 0 { 79 | return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") 80 | } 81 | configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) 82 | } 83 | 84 | var cs Clientset 85 | var err error 86 | cs.autoscalingV1alpha1, err = autoscalingv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient) 92 | if err != nil { 93 | return nil, err 94 | } 95 | return &cs, nil 96 | } 97 | 98 | // NewForConfigOrDie creates a new Clientset for the given config and 99 | // panics if there is an error in the config. 100 | func NewForConfigOrDie(c *rest.Config) *Clientset { 101 | cs, err := NewForConfig(c) 102 | if err != nil { 103 | panic(err) 104 | } 105 | return cs 106 | } 107 | 108 | // New creates a new Clientset for the given RESTClient. 109 | func New(c rest.Interface) *Clientset { 110 | var cs Clientset 111 | cs.autoscalingV1alpha1 = autoscalingv1alpha1.New(c) 112 | 113 | cs.DiscoveryClient = discovery.NewDiscoveryClient(c) 114 | return &cs 115 | } 116 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | // This package has the automatically generated clientset. 18 | package versioned 19 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/fake/clientset_generated.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | package fake 18 | 19 | import ( 20 | clientset "github.com/ocgi/general-pod-autoscaler/pkg/client/clientset/versioned" 21 | autoscalingv1alpha1 "github.com/ocgi/general-pod-autoscaler/pkg/client/clientset/versioned/typed/autoscaling/v1alpha1" 22 | fakeautoscalingv1alpha1 "github.com/ocgi/general-pod-autoscaler/pkg/client/clientset/versioned/typed/autoscaling/v1alpha1/fake" 23 | "k8s.io/apimachinery/pkg/runtime" 24 | "k8s.io/apimachinery/pkg/watch" 25 | "k8s.io/client-go/discovery" 26 | fakediscovery "k8s.io/client-go/discovery/fake" 27 | "k8s.io/client-go/testing" 28 | ) 29 | 30 | // NewSimpleClientset returns a clientset that will respond with the provided objects. 31 | // It's backed by a very simple object tracker that processes creates, updates and deletions as-is, 32 | // without applying any validations and/or defaults. It shouldn't be considered a replacement 33 | // for a real clientset and is mostly useful in simple unit tests. 34 | func NewSimpleClientset(objects ...runtime.Object) *Clientset { 35 | o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) 36 | for _, obj := range objects { 37 | if err := o.Add(obj); err != nil { 38 | panic(err) 39 | } 40 | } 41 | 42 | cs := &Clientset{tracker: o} 43 | cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} 44 | cs.AddReactor("*", "*", testing.ObjectReaction(o)) 45 | cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { 46 | gvr := action.GetResource() 47 | ns := action.GetNamespace() 48 | watch, err := o.Watch(gvr, ns) 49 | if err != nil { 50 | return false, nil, err 51 | } 52 | return true, watch, nil 53 | }) 54 | 55 | return cs 56 | } 57 | 58 | // Clientset implements clientset.Interface. Meant to be embedded into a 59 | // struct to get a default implementation. This makes faking out just the method 60 | // you want to test easier. 61 | type Clientset struct { 62 | testing.Fake 63 | discovery *fakediscovery.FakeDiscovery 64 | tracker testing.ObjectTracker 65 | } 66 | 67 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 68 | return c.discovery 69 | } 70 | 71 | func (c *Clientset) Tracker() testing.ObjectTracker { 72 | return c.tracker 73 | } 74 | 75 | var ( 76 | _ clientset.Interface = &Clientset{} 77 | _ testing.FakeClient = &Clientset{} 78 | ) 79 | 80 | // AutoscalingV1alpha1 retrieves the AutoscalingV1alpha1Client 81 | func (c *Clientset) AutoscalingV1alpha1() autoscalingv1alpha1.AutoscalingV1alpha1Interface { 82 | return &fakeautoscalingv1alpha1.FakeAutoscalingV1alpha1{Fake: &c.Fake} 83 | } 84 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/fake/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | // This package has the automatically generated fake clientset. 18 | package fake 19 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/fake/register.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | package fake 18 | 19 | import ( 20 | autoscalingv1alpha1 "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling/v1alpha1" 21 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | runtime "k8s.io/apimachinery/pkg/runtime" 23 | schema "k8s.io/apimachinery/pkg/runtime/schema" 24 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 25 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 26 | ) 27 | 28 | var scheme = runtime.NewScheme() 29 | var codecs = serializer.NewCodecFactory(scheme) 30 | 31 | var localSchemeBuilder = runtime.SchemeBuilder{ 32 | autoscalingv1alpha1.AddToScheme, 33 | } 34 | 35 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 36 | // of clientsets, like in: 37 | // 38 | // import ( 39 | // "k8s.io/client-go/kubernetes" 40 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 41 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 42 | // ) 43 | // 44 | // kclientset, _ := kubernetes.NewForConfig(c) 45 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 46 | // 47 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 48 | // correctly. 49 | var AddToScheme = localSchemeBuilder.AddToScheme 50 | 51 | func init() { 52 | v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) 53 | utilruntime.Must(AddToScheme(scheme)) 54 | } 55 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/scheme/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | // This package contains the scheme of the automatically generated clientset. 18 | package scheme 19 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/scheme/register.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | package scheme 18 | 19 | import ( 20 | autoscalingv1alpha1 "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling/v1alpha1" 21 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | runtime "k8s.io/apimachinery/pkg/runtime" 23 | schema "k8s.io/apimachinery/pkg/runtime/schema" 24 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 25 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 26 | ) 27 | 28 | var Scheme = runtime.NewScheme() 29 | var Codecs = serializer.NewCodecFactory(Scheme) 30 | var ParameterCodec = runtime.NewParameterCodec(Scheme) 31 | var localSchemeBuilder = runtime.SchemeBuilder{ 32 | autoscalingv1alpha1.AddToScheme, 33 | } 34 | 35 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 36 | // of clientsets, like in: 37 | // 38 | // import ( 39 | // "k8s.io/client-go/kubernetes" 40 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 41 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 42 | // ) 43 | // 44 | // kclientset, _ := kubernetes.NewForConfig(c) 45 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 46 | // 47 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 48 | // correctly. 49 | var AddToScheme = localSchemeBuilder.AddToScheme 50 | 51 | func init() { 52 | v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) 53 | utilruntime.Must(AddToScheme(Scheme)) 54 | } 55 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/autoscaling/v1alpha1/autoscaling_client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "net/http" 21 | 22 | v1alpha1 "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling/v1alpha1" 23 | "github.com/ocgi/general-pod-autoscaler/pkg/client/clientset/versioned/scheme" 24 | rest "k8s.io/client-go/rest" 25 | ) 26 | 27 | type AutoscalingV1alpha1Interface interface { 28 | RESTClient() rest.Interface 29 | GeneralPodAutoscalersGetter 30 | } 31 | 32 | // AutoscalingV1alpha1Client is used to interact with features provided by the autoscaling.ocgi.dev group. 33 | type AutoscalingV1alpha1Client struct { 34 | restClient rest.Interface 35 | } 36 | 37 | func (c *AutoscalingV1alpha1Client) GeneralPodAutoscalers(namespace string) GeneralPodAutoscalerInterface { 38 | return newGeneralPodAutoscalers(c, namespace) 39 | } 40 | 41 | // NewForConfig creates a new AutoscalingV1alpha1Client for the given config. 42 | // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), 43 | // where httpClient was generated with rest.HTTPClientFor(c). 44 | func NewForConfig(c *rest.Config) (*AutoscalingV1alpha1Client, error) { 45 | config := *c 46 | if err := setConfigDefaults(&config); err != nil { 47 | return nil, err 48 | } 49 | httpClient, err := rest.HTTPClientFor(&config) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return NewForConfigAndClient(&config, httpClient) 54 | } 55 | 56 | // NewForConfigAndClient creates a new AutoscalingV1alpha1Client for the given config and http client. 57 | // Note the http client provided takes precedence over the configured transport values. 58 | func NewForConfigAndClient(c *rest.Config, h *http.Client) (*AutoscalingV1alpha1Client, error) { 59 | config := *c 60 | if err := setConfigDefaults(&config); err != nil { 61 | return nil, err 62 | } 63 | client, err := rest.RESTClientForConfigAndClient(&config, h) 64 | if err != nil { 65 | return nil, err 66 | } 67 | return &AutoscalingV1alpha1Client{client}, nil 68 | } 69 | 70 | // NewForConfigOrDie creates a new AutoscalingV1alpha1Client for the given config and 71 | // panics if there is an error in the config. 72 | func NewForConfigOrDie(c *rest.Config) *AutoscalingV1alpha1Client { 73 | client, err := NewForConfig(c) 74 | if err != nil { 75 | panic(err) 76 | } 77 | return client 78 | } 79 | 80 | // New creates a new AutoscalingV1alpha1Client for the given RESTClient. 81 | func New(c rest.Interface) *AutoscalingV1alpha1Client { 82 | return &AutoscalingV1alpha1Client{c} 83 | } 84 | 85 | func setConfigDefaults(config *rest.Config) error { 86 | gv := v1alpha1.SchemeGroupVersion 87 | config.GroupVersion = &gv 88 | config.APIPath = "/apis" 89 | config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() 90 | 91 | if config.UserAgent == "" { 92 | config.UserAgent = rest.DefaultKubernetesUserAgent() 93 | } 94 | 95 | return nil 96 | } 97 | 98 | // RESTClient returns a RESTClient that is used to communicate 99 | // with API server by this client implementation. 100 | func (c *AutoscalingV1alpha1Client) RESTClient() rest.Interface { 101 | if c == nil { 102 | return nil 103 | } 104 | return c.restClient 105 | } 106 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/autoscaling/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | // This package has the automatically generated typed clients. 18 | package v1alpha1 19 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/autoscaling/v1alpha1/fake/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | // Package fake has the automatically generated clients. 18 | package fake 19 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/autoscaling/v1alpha1/fake/fake_autoscaling_client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | package fake 18 | 19 | import ( 20 | v1alpha1 "github.com/ocgi/general-pod-autoscaler/pkg/client/clientset/versioned/typed/autoscaling/v1alpha1" 21 | rest "k8s.io/client-go/rest" 22 | testing "k8s.io/client-go/testing" 23 | ) 24 | 25 | type FakeAutoscalingV1alpha1 struct { 26 | *testing.Fake 27 | } 28 | 29 | func (c *FakeAutoscalingV1alpha1) GeneralPodAutoscalers(namespace string) v1alpha1.GeneralPodAutoscalerInterface { 30 | return &FakeGeneralPodAutoscalers{c, namespace} 31 | } 32 | 33 | // RESTClient returns a RESTClient that is used to communicate 34 | // with API server by this client implementation. 35 | func (c *FakeAutoscalingV1alpha1) RESTClient() rest.Interface { 36 | var ret *rest.RESTClient 37 | return ret 38 | } 39 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/autoscaling/v1alpha1/fake/fake_generalpodautoscaler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | package fake 18 | 19 | import ( 20 | "context" 21 | 22 | v1alpha1 "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling/v1alpha1" 23 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | labels "k8s.io/apimachinery/pkg/labels" 25 | schema "k8s.io/apimachinery/pkg/runtime/schema" 26 | types "k8s.io/apimachinery/pkg/types" 27 | watch "k8s.io/apimachinery/pkg/watch" 28 | testing "k8s.io/client-go/testing" 29 | ) 30 | 31 | // FakeGeneralPodAutoscalers implements GeneralPodAutoscalerInterface 32 | type FakeGeneralPodAutoscalers struct { 33 | Fake *FakeAutoscalingV1alpha1 34 | ns string 35 | } 36 | 37 | var generalpodautoscalersResource = schema.GroupVersionResource{Group: "autoscaling.ocgi.dev", Version: "v1alpha1", Resource: "generalpodautoscalers"} 38 | 39 | var generalpodautoscalersKind = schema.GroupVersionKind{Group: "autoscaling.ocgi.dev", Version: "v1alpha1", Kind: "GeneralPodAutoscaler"} 40 | 41 | // Get takes name of the generalPodAutoscaler, and returns the corresponding generalPodAutoscaler object, and an error if there is any. 42 | func (c *FakeGeneralPodAutoscalers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.GeneralPodAutoscaler, err error) { 43 | obj, err := c.Fake. 44 | Invokes(testing.NewGetAction(generalpodautoscalersResource, c.ns, name), &v1alpha1.GeneralPodAutoscaler{}) 45 | 46 | if obj == nil { 47 | return nil, err 48 | } 49 | return obj.(*v1alpha1.GeneralPodAutoscaler), err 50 | } 51 | 52 | // List takes label and field selectors, and returns the list of GeneralPodAutoscalers that match those selectors. 53 | func (c *FakeGeneralPodAutoscalers) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.GeneralPodAutoscalerList, err error) { 54 | obj, err := c.Fake. 55 | Invokes(testing.NewListAction(generalpodautoscalersResource, generalpodautoscalersKind, c.ns, opts), &v1alpha1.GeneralPodAutoscalerList{}) 56 | 57 | if obj == nil { 58 | return nil, err 59 | } 60 | 61 | label, _, _ := testing.ExtractFromListOptions(opts) 62 | if label == nil { 63 | label = labels.Everything() 64 | } 65 | list := &v1alpha1.GeneralPodAutoscalerList{ListMeta: obj.(*v1alpha1.GeneralPodAutoscalerList).ListMeta} 66 | for _, item := range obj.(*v1alpha1.GeneralPodAutoscalerList).Items { 67 | if label.Matches(labels.Set(item.Labels)) { 68 | list.Items = append(list.Items, item) 69 | } 70 | } 71 | return list, err 72 | } 73 | 74 | // Watch returns a watch.Interface that watches the requested generalPodAutoscalers. 75 | func (c *FakeGeneralPodAutoscalers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { 76 | return c.Fake. 77 | InvokesWatch(testing.NewWatchAction(generalpodautoscalersResource, c.ns, opts)) 78 | 79 | } 80 | 81 | // Create takes the representation of a generalPodAutoscaler and creates it. Returns the server's representation of the generalPodAutoscaler, and an error, if there is any. 82 | func (c *FakeGeneralPodAutoscalers) Create(ctx context.Context, generalPodAutoscaler *v1alpha1.GeneralPodAutoscaler, opts v1.CreateOptions) (result *v1alpha1.GeneralPodAutoscaler, err error) { 83 | obj, err := c.Fake. 84 | Invokes(testing.NewCreateAction(generalpodautoscalersResource, c.ns, generalPodAutoscaler), &v1alpha1.GeneralPodAutoscaler{}) 85 | 86 | if obj == nil { 87 | return nil, err 88 | } 89 | return obj.(*v1alpha1.GeneralPodAutoscaler), err 90 | } 91 | 92 | // Update takes the representation of a generalPodAutoscaler and updates it. Returns the server's representation of the generalPodAutoscaler, and an error, if there is any. 93 | func (c *FakeGeneralPodAutoscalers) Update(ctx context.Context, generalPodAutoscaler *v1alpha1.GeneralPodAutoscaler, opts v1.UpdateOptions) (result *v1alpha1.GeneralPodAutoscaler, err error) { 94 | obj, err := c.Fake. 95 | Invokes(testing.NewUpdateAction(generalpodautoscalersResource, c.ns, generalPodAutoscaler), &v1alpha1.GeneralPodAutoscaler{}) 96 | 97 | if obj == nil { 98 | return nil, err 99 | } 100 | return obj.(*v1alpha1.GeneralPodAutoscaler), err 101 | } 102 | 103 | // UpdateStatus was generated because the type contains a Status member. 104 | // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). 105 | func (c *FakeGeneralPodAutoscalers) UpdateStatus(ctx context.Context, generalPodAutoscaler *v1alpha1.GeneralPodAutoscaler, opts v1.UpdateOptions) (*v1alpha1.GeneralPodAutoscaler, error) { 106 | obj, err := c.Fake. 107 | Invokes(testing.NewUpdateSubresourceAction(generalpodautoscalersResource, "status", c.ns, generalPodAutoscaler), &v1alpha1.GeneralPodAutoscaler{}) 108 | 109 | if obj == nil { 110 | return nil, err 111 | } 112 | return obj.(*v1alpha1.GeneralPodAutoscaler), err 113 | } 114 | 115 | // Delete takes name of the generalPodAutoscaler and deletes it. Returns an error if one occurs. 116 | func (c *FakeGeneralPodAutoscalers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { 117 | _, err := c.Fake. 118 | Invokes(testing.NewDeleteActionWithOptions(generalpodautoscalersResource, c.ns, name, opts), &v1alpha1.GeneralPodAutoscaler{}) 119 | 120 | return err 121 | } 122 | 123 | // DeleteCollection deletes a collection of objects. 124 | func (c *FakeGeneralPodAutoscalers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { 125 | action := testing.NewDeleteCollectionAction(generalpodautoscalersResource, c.ns, listOpts) 126 | 127 | _, err := c.Fake.Invokes(action, &v1alpha1.GeneralPodAutoscalerList{}) 128 | return err 129 | } 130 | 131 | // Patch applies the patch and returns the patched generalPodAutoscaler. 132 | func (c *FakeGeneralPodAutoscalers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.GeneralPodAutoscaler, err error) { 133 | obj, err := c.Fake. 134 | Invokes(testing.NewPatchSubresourceAction(generalpodautoscalersResource, c.ns, name, pt, data, subresources...), &v1alpha1.GeneralPodAutoscaler{}) 135 | 136 | if obj == nil { 137 | return nil, err 138 | } 139 | return obj.(*v1alpha1.GeneralPodAutoscaler), err 140 | } 141 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/autoscaling/v1alpha1/generalpodautoscaler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "context" 21 | "time" 22 | 23 | v1alpha1 "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling/v1alpha1" 24 | scheme "github.com/ocgi/general-pod-autoscaler/pkg/client/clientset/versioned/scheme" 25 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | types "k8s.io/apimachinery/pkg/types" 27 | watch "k8s.io/apimachinery/pkg/watch" 28 | rest "k8s.io/client-go/rest" 29 | ) 30 | 31 | // GeneralPodAutoscalersGetter has a method to return a GeneralPodAutoscalerInterface. 32 | // A group's client should implement this interface. 33 | type GeneralPodAutoscalersGetter interface { 34 | GeneralPodAutoscalers(namespace string) GeneralPodAutoscalerInterface 35 | } 36 | 37 | // GeneralPodAutoscalerInterface has methods to work with GeneralPodAutoscaler resources. 38 | type GeneralPodAutoscalerInterface interface { 39 | Create(ctx context.Context, generalPodAutoscaler *v1alpha1.GeneralPodAutoscaler, opts v1.CreateOptions) (*v1alpha1.GeneralPodAutoscaler, error) 40 | Update(ctx context.Context, generalPodAutoscaler *v1alpha1.GeneralPodAutoscaler, opts v1.UpdateOptions) (*v1alpha1.GeneralPodAutoscaler, error) 41 | UpdateStatus(ctx context.Context, generalPodAutoscaler *v1alpha1.GeneralPodAutoscaler, opts v1.UpdateOptions) (*v1alpha1.GeneralPodAutoscaler, error) 42 | Delete(ctx context.Context, name string, opts v1.DeleteOptions) error 43 | DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error 44 | Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.GeneralPodAutoscaler, error) 45 | List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.GeneralPodAutoscalerList, error) 46 | Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) 47 | Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.GeneralPodAutoscaler, err error) 48 | GeneralPodAutoscalerExpansion 49 | } 50 | 51 | // generalPodAutoscalers implements GeneralPodAutoscalerInterface 52 | type generalPodAutoscalers struct { 53 | client rest.Interface 54 | ns string 55 | } 56 | 57 | // newGeneralPodAutoscalers returns a GeneralPodAutoscalers 58 | func newGeneralPodAutoscalers(c *AutoscalingV1alpha1Client, namespace string) *generalPodAutoscalers { 59 | return &generalPodAutoscalers{ 60 | client: c.RESTClient(), 61 | ns: namespace, 62 | } 63 | } 64 | 65 | // Get takes name of the generalPodAutoscaler, and returns the corresponding generalPodAutoscaler object, and an error if there is any. 66 | func (c *generalPodAutoscalers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.GeneralPodAutoscaler, err error) { 67 | result = &v1alpha1.GeneralPodAutoscaler{} 68 | err = c.client.Get(). 69 | Namespace(c.ns). 70 | Resource("generalpodautoscalers"). 71 | Name(name). 72 | VersionedParams(&options, scheme.ParameterCodec). 73 | Do(ctx). 74 | Into(result) 75 | return 76 | } 77 | 78 | // List takes label and field selectors, and returns the list of GeneralPodAutoscalers that match those selectors. 79 | func (c *generalPodAutoscalers) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.GeneralPodAutoscalerList, err error) { 80 | var timeout time.Duration 81 | if opts.TimeoutSeconds != nil { 82 | timeout = time.Duration(*opts.TimeoutSeconds) * time.Second 83 | } 84 | result = &v1alpha1.GeneralPodAutoscalerList{} 85 | err = c.client.Get(). 86 | Namespace(c.ns). 87 | Resource("generalpodautoscalers"). 88 | VersionedParams(&opts, scheme.ParameterCodec). 89 | Timeout(timeout). 90 | Do(ctx). 91 | Into(result) 92 | return 93 | } 94 | 95 | // Watch returns a watch.Interface that watches the requested generalPodAutoscalers. 96 | func (c *generalPodAutoscalers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { 97 | var timeout time.Duration 98 | if opts.TimeoutSeconds != nil { 99 | timeout = time.Duration(*opts.TimeoutSeconds) * time.Second 100 | } 101 | opts.Watch = true 102 | return c.client.Get(). 103 | Namespace(c.ns). 104 | Resource("generalpodautoscalers"). 105 | VersionedParams(&opts, scheme.ParameterCodec). 106 | Timeout(timeout). 107 | Watch(ctx) 108 | } 109 | 110 | // Create takes the representation of a generalPodAutoscaler and creates it. Returns the server's representation of the generalPodAutoscaler, and an error, if there is any. 111 | func (c *generalPodAutoscalers) Create(ctx context.Context, generalPodAutoscaler *v1alpha1.GeneralPodAutoscaler, opts v1.CreateOptions) (result *v1alpha1.GeneralPodAutoscaler, err error) { 112 | result = &v1alpha1.GeneralPodAutoscaler{} 113 | err = c.client.Post(). 114 | Namespace(c.ns). 115 | Resource("generalpodautoscalers"). 116 | VersionedParams(&opts, scheme.ParameterCodec). 117 | Body(generalPodAutoscaler). 118 | Do(ctx). 119 | Into(result) 120 | return 121 | } 122 | 123 | // Update takes the representation of a generalPodAutoscaler and updates it. Returns the server's representation of the generalPodAutoscaler, and an error, if there is any. 124 | func (c *generalPodAutoscalers) Update(ctx context.Context, generalPodAutoscaler *v1alpha1.GeneralPodAutoscaler, opts v1.UpdateOptions) (result *v1alpha1.GeneralPodAutoscaler, err error) { 125 | result = &v1alpha1.GeneralPodAutoscaler{} 126 | err = c.client.Put(). 127 | Namespace(c.ns). 128 | Resource("generalpodautoscalers"). 129 | Name(generalPodAutoscaler.Name). 130 | VersionedParams(&opts, scheme.ParameterCodec). 131 | Body(generalPodAutoscaler). 132 | Do(ctx). 133 | Into(result) 134 | return 135 | } 136 | 137 | // UpdateStatus was generated because the type contains a Status member. 138 | // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). 139 | func (c *generalPodAutoscalers) UpdateStatus(ctx context.Context, generalPodAutoscaler *v1alpha1.GeneralPodAutoscaler, opts v1.UpdateOptions) (result *v1alpha1.GeneralPodAutoscaler, err error) { 140 | result = &v1alpha1.GeneralPodAutoscaler{} 141 | err = c.client.Put(). 142 | Namespace(c.ns). 143 | Resource("generalpodautoscalers"). 144 | Name(generalPodAutoscaler.Name). 145 | SubResource("status"). 146 | VersionedParams(&opts, scheme.ParameterCodec). 147 | Body(generalPodAutoscaler). 148 | Do(ctx). 149 | Into(result) 150 | return 151 | } 152 | 153 | // Delete takes name of the generalPodAutoscaler and deletes it. Returns an error if one occurs. 154 | func (c *generalPodAutoscalers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { 155 | return c.client.Delete(). 156 | Namespace(c.ns). 157 | Resource("generalpodautoscalers"). 158 | Name(name). 159 | Body(&opts). 160 | Do(ctx). 161 | Error() 162 | } 163 | 164 | // DeleteCollection deletes a collection of objects. 165 | func (c *generalPodAutoscalers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { 166 | var timeout time.Duration 167 | if listOpts.TimeoutSeconds != nil { 168 | timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second 169 | } 170 | return c.client.Delete(). 171 | Namespace(c.ns). 172 | Resource("generalpodautoscalers"). 173 | VersionedParams(&listOpts, scheme.ParameterCodec). 174 | Timeout(timeout). 175 | Body(&opts). 176 | Do(ctx). 177 | Error() 178 | } 179 | 180 | // Patch applies the patch and returns the patched generalPodAutoscaler. 181 | func (c *generalPodAutoscalers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.GeneralPodAutoscaler, err error) { 182 | result = &v1alpha1.GeneralPodAutoscaler{} 183 | err = c.client.Patch(pt). 184 | Namespace(c.ns). 185 | Resource("generalpodautoscalers"). 186 | Name(name). 187 | SubResource(subresources...). 188 | VersionedParams(&opts, scheme.ParameterCodec). 189 | Body(data). 190 | Do(ctx). 191 | Into(result) 192 | return 193 | } 194 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/autoscaling/v1alpha1/generated_expansion.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | package v1alpha1 18 | 19 | type GeneralPodAutoscalerExpansion interface{} 20 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/autoscaling/interface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by informer-gen. DO NOT EDIT. 16 | 17 | package autoscaling 18 | 19 | import ( 20 | v1alpha1 "github.com/ocgi/general-pod-autoscaler/pkg/client/informers/externalversions/autoscaling/v1alpha1" 21 | internalinterfaces "github.com/ocgi/general-pod-autoscaler/pkg/client/informers/externalversions/internalinterfaces" 22 | ) 23 | 24 | // Interface provides access to each of this group's versions. 25 | type Interface interface { 26 | // V1alpha1 provides access to shared informers for resources in V1alpha1. 27 | V1alpha1() v1alpha1.Interface 28 | } 29 | 30 | type group struct { 31 | factory internalinterfaces.SharedInformerFactory 32 | namespace string 33 | tweakListOptions internalinterfaces.TweakListOptionsFunc 34 | } 35 | 36 | // New returns a new Interface. 37 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 38 | return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 39 | } 40 | 41 | // V1alpha1 returns a new v1alpha1.Interface. 42 | func (g *group) V1alpha1() v1alpha1.Interface { 43 | return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) 44 | } 45 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/autoscaling/v1alpha1/generalpodautoscaler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by informer-gen. DO NOT EDIT. 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "context" 21 | time "time" 22 | 23 | autoscalingv1alpha1 "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling/v1alpha1" 24 | versioned "github.com/ocgi/general-pod-autoscaler/pkg/client/clientset/versioned" 25 | internalinterfaces "github.com/ocgi/general-pod-autoscaler/pkg/client/informers/externalversions/internalinterfaces" 26 | v1alpha1 "github.com/ocgi/general-pod-autoscaler/pkg/client/listers/autoscaling/v1alpha1" 27 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | runtime "k8s.io/apimachinery/pkg/runtime" 29 | watch "k8s.io/apimachinery/pkg/watch" 30 | cache "k8s.io/client-go/tools/cache" 31 | ) 32 | 33 | // GeneralPodAutoscalerInformer provides access to a shared informer and lister for 34 | // GeneralPodAutoscalers. 35 | type GeneralPodAutoscalerInformer interface { 36 | Informer() cache.SharedIndexInformer 37 | Lister() v1alpha1.GeneralPodAutoscalerLister 38 | } 39 | 40 | type generalPodAutoscalerInformer struct { 41 | factory internalinterfaces.SharedInformerFactory 42 | tweakListOptions internalinterfaces.TweakListOptionsFunc 43 | namespace string 44 | } 45 | 46 | // NewGeneralPodAutoscalerInformer constructs a new informer for GeneralPodAutoscaler type. 47 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 48 | // one. This reduces memory footprint and number of connections to the server. 49 | func NewGeneralPodAutoscalerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { 50 | return NewFilteredGeneralPodAutoscalerInformer(client, namespace, resyncPeriod, indexers, nil) 51 | } 52 | 53 | // NewFilteredGeneralPodAutoscalerInformer constructs a new informer for GeneralPodAutoscaler type. 54 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 55 | // one. This reduces memory footprint and number of connections to the server. 56 | func NewFilteredGeneralPodAutoscalerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { 57 | return cache.NewSharedIndexInformer( 58 | &cache.ListWatch{ 59 | ListFunc: func(options v1.ListOptions) (runtime.Object, error) { 60 | if tweakListOptions != nil { 61 | tweakListOptions(&options) 62 | } 63 | return client.AutoscalingV1alpha1().GeneralPodAutoscalers(namespace).List(context.TODO(), options) 64 | }, 65 | WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { 66 | if tweakListOptions != nil { 67 | tweakListOptions(&options) 68 | } 69 | return client.AutoscalingV1alpha1().GeneralPodAutoscalers(namespace).Watch(context.TODO(), options) 70 | }, 71 | }, 72 | &autoscalingv1alpha1.GeneralPodAutoscaler{}, 73 | resyncPeriod, 74 | indexers, 75 | ) 76 | } 77 | 78 | func (f *generalPodAutoscalerInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { 79 | return NewFilteredGeneralPodAutoscalerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) 80 | } 81 | 82 | func (f *generalPodAutoscalerInformer) Informer() cache.SharedIndexInformer { 83 | return f.factory.InformerFor(&autoscalingv1alpha1.GeneralPodAutoscaler{}, f.defaultInformer) 84 | } 85 | 86 | func (f *generalPodAutoscalerInformer) Lister() v1alpha1.GeneralPodAutoscalerLister { 87 | return v1alpha1.NewGeneralPodAutoscalerLister(f.Informer().GetIndexer()) 88 | } 89 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/autoscaling/v1alpha1/interface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by informer-gen. DO NOT EDIT. 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | internalinterfaces "github.com/ocgi/general-pod-autoscaler/pkg/client/informers/externalversions/internalinterfaces" 21 | ) 22 | 23 | // Interface provides access to all the informers in this group version. 24 | type Interface interface { 25 | // GeneralPodAutoscalers returns a GeneralPodAutoscalerInformer. 26 | GeneralPodAutoscalers() GeneralPodAutoscalerInformer 27 | } 28 | 29 | type version struct { 30 | factory internalinterfaces.SharedInformerFactory 31 | namespace string 32 | tweakListOptions internalinterfaces.TweakListOptionsFunc 33 | } 34 | 35 | // New returns a new Interface. 36 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 37 | return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 38 | } 39 | 40 | // GeneralPodAutoscalers returns a GeneralPodAutoscalerInformer. 41 | func (v *version) GeneralPodAutoscalers() GeneralPodAutoscalerInformer { 42 | return &generalPodAutoscalerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} 43 | } 44 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/factory.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by informer-gen. DO NOT EDIT. 16 | 17 | package externalversions 18 | 19 | import ( 20 | reflect "reflect" 21 | sync "sync" 22 | time "time" 23 | 24 | versioned "github.com/ocgi/general-pod-autoscaler/pkg/client/clientset/versioned" 25 | autoscaling "github.com/ocgi/general-pod-autoscaler/pkg/client/informers/externalversions/autoscaling" 26 | internalinterfaces "github.com/ocgi/general-pod-autoscaler/pkg/client/informers/externalversions/internalinterfaces" 27 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | runtime "k8s.io/apimachinery/pkg/runtime" 29 | schema "k8s.io/apimachinery/pkg/runtime/schema" 30 | cache "k8s.io/client-go/tools/cache" 31 | ) 32 | 33 | // SharedInformerOption defines the functional option type for SharedInformerFactory. 34 | type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory 35 | 36 | type sharedInformerFactory struct { 37 | client versioned.Interface 38 | namespace string 39 | tweakListOptions internalinterfaces.TweakListOptionsFunc 40 | lock sync.Mutex 41 | defaultResync time.Duration 42 | customResync map[reflect.Type]time.Duration 43 | 44 | informers map[reflect.Type]cache.SharedIndexInformer 45 | // startedInformers is used for tracking which informers have been started. 46 | // This allows Start() to be called multiple times safely. 47 | startedInformers map[reflect.Type]bool 48 | } 49 | 50 | // WithCustomResyncConfig sets a custom resync period for the specified informer types. 51 | func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption { 52 | return func(factory *sharedInformerFactory) *sharedInformerFactory { 53 | for k, v := range resyncConfig { 54 | factory.customResync[reflect.TypeOf(k)] = v 55 | } 56 | return factory 57 | } 58 | } 59 | 60 | // WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory. 61 | func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption { 62 | return func(factory *sharedInformerFactory) *sharedInformerFactory { 63 | factory.tweakListOptions = tweakListOptions 64 | return factory 65 | } 66 | } 67 | 68 | // WithNamespace limits the SharedInformerFactory to the specified namespace. 69 | func WithNamespace(namespace string) SharedInformerOption { 70 | return func(factory *sharedInformerFactory) *sharedInformerFactory { 71 | factory.namespace = namespace 72 | return factory 73 | } 74 | } 75 | 76 | // NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. 77 | func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory { 78 | return NewSharedInformerFactoryWithOptions(client, defaultResync) 79 | } 80 | 81 | // NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory. 82 | // Listers obtained via this SharedInformerFactory will be subject to the same filters 83 | // as specified here. 84 | // Deprecated: Please use NewSharedInformerFactoryWithOptions instead 85 | func NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory { 86 | return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions)) 87 | } 88 | 89 | // NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options. 90 | func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory { 91 | factory := &sharedInformerFactory{ 92 | client: client, 93 | namespace: v1.NamespaceAll, 94 | defaultResync: defaultResync, 95 | informers: make(map[reflect.Type]cache.SharedIndexInformer), 96 | startedInformers: make(map[reflect.Type]bool), 97 | customResync: make(map[reflect.Type]time.Duration), 98 | } 99 | 100 | // Apply all options 101 | for _, opt := range options { 102 | factory = opt(factory) 103 | } 104 | 105 | return factory 106 | } 107 | 108 | // Start initializes all requested informers. 109 | func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { 110 | f.lock.Lock() 111 | defer f.lock.Unlock() 112 | 113 | for informerType, informer := range f.informers { 114 | if !f.startedInformers[informerType] { 115 | go informer.Run(stopCh) 116 | f.startedInformers[informerType] = true 117 | } 118 | } 119 | } 120 | 121 | // WaitForCacheSync waits for all started informers' cache were synced. 122 | func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { 123 | informers := func() map[reflect.Type]cache.SharedIndexInformer { 124 | f.lock.Lock() 125 | defer f.lock.Unlock() 126 | 127 | informers := map[reflect.Type]cache.SharedIndexInformer{} 128 | for informerType, informer := range f.informers { 129 | if f.startedInformers[informerType] { 130 | informers[informerType] = informer 131 | } 132 | } 133 | return informers 134 | }() 135 | 136 | res := map[reflect.Type]bool{} 137 | for informType, informer := range informers { 138 | res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced) 139 | } 140 | return res 141 | } 142 | 143 | // InternalInformerFor returns the SharedIndexInformer for obj using an internal 144 | // client. 145 | func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { 146 | f.lock.Lock() 147 | defer f.lock.Unlock() 148 | 149 | informerType := reflect.TypeOf(obj) 150 | informer, exists := f.informers[informerType] 151 | if exists { 152 | return informer 153 | } 154 | 155 | resyncPeriod, exists := f.customResync[informerType] 156 | if !exists { 157 | resyncPeriod = f.defaultResync 158 | } 159 | 160 | informer = newFunc(f.client, resyncPeriod) 161 | f.informers[informerType] = informer 162 | 163 | return informer 164 | } 165 | 166 | // SharedInformerFactory provides shared informers for resources in all known 167 | // API group versions. 168 | type SharedInformerFactory interface { 169 | internalinterfaces.SharedInformerFactory 170 | ForResource(resource schema.GroupVersionResource) (GenericInformer, error) 171 | WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool 172 | 173 | Autoscaling() autoscaling.Interface 174 | } 175 | 176 | func (f *sharedInformerFactory) Autoscaling() autoscaling.Interface { 177 | return autoscaling.New(f, f.namespace, f.tweakListOptions) 178 | } 179 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/generic.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by informer-gen. DO NOT EDIT. 16 | 17 | package externalversions 18 | 19 | import ( 20 | "fmt" 21 | 22 | v1alpha1 "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling/v1alpha1" 23 | schema "k8s.io/apimachinery/pkg/runtime/schema" 24 | cache "k8s.io/client-go/tools/cache" 25 | ) 26 | 27 | // GenericInformer is type of SharedIndexInformer which will locate and delegate to other 28 | // sharedInformers based on type 29 | type GenericInformer interface { 30 | Informer() cache.SharedIndexInformer 31 | Lister() cache.GenericLister 32 | } 33 | 34 | type genericInformer struct { 35 | informer cache.SharedIndexInformer 36 | resource schema.GroupResource 37 | } 38 | 39 | // Informer returns the SharedIndexInformer. 40 | func (f *genericInformer) Informer() cache.SharedIndexInformer { 41 | return f.informer 42 | } 43 | 44 | // Lister returns the GenericLister. 45 | func (f *genericInformer) Lister() cache.GenericLister { 46 | return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) 47 | } 48 | 49 | // ForResource gives generic access to a shared informer of the matching type 50 | // TODO extend this to unknown resources with a client pool 51 | func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { 52 | switch resource { 53 | // Group=autoscaling.ocgi.dev, Version=v1alpha1 54 | case v1alpha1.SchemeGroupVersion.WithResource("generalpodautoscalers"): 55 | return &genericInformer{resource: resource.GroupResource(), informer: f.Autoscaling().V1alpha1().GeneralPodAutoscalers().Informer()}, nil 56 | 57 | } 58 | 59 | return nil, fmt.Errorf("no informer found for %v", resource) 60 | } 61 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by informer-gen. DO NOT EDIT. 16 | 17 | package internalinterfaces 18 | 19 | import ( 20 | time "time" 21 | 22 | versioned "github.com/ocgi/general-pod-autoscaler/pkg/client/clientset/versioned" 23 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | runtime "k8s.io/apimachinery/pkg/runtime" 25 | cache "k8s.io/client-go/tools/cache" 26 | ) 27 | 28 | // NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. 29 | type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer 30 | 31 | // SharedInformerFactory a small interface to allow for adding an informer without an import cycle 32 | type SharedInformerFactory interface { 33 | Start(stopCh <-chan struct{}) 34 | InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer 35 | } 36 | 37 | // TweakListOptionsFunc is a function that transforms a v1.ListOptions. 38 | type TweakListOptionsFunc func(*v1.ListOptions) 39 | -------------------------------------------------------------------------------- /pkg/client/listers/autoscaling/v1alpha1/expansion_generated.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by lister-gen. DO NOT EDIT. 16 | 17 | package v1alpha1 18 | 19 | // GeneralPodAutoscalerListerExpansion allows custom methods to be added to 20 | // GeneralPodAutoscalerLister. 21 | type GeneralPodAutoscalerListerExpansion interface{} 22 | 23 | // GeneralPodAutoscalerNamespaceListerExpansion allows custom methods to be added to 24 | // GeneralPodAutoscalerNamespaceLister. 25 | type GeneralPodAutoscalerNamespaceListerExpansion interface{} 26 | -------------------------------------------------------------------------------- /pkg/client/listers/autoscaling/v1alpha1/generalpodautoscaler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by lister-gen. DO NOT EDIT. 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | v1alpha1 "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling/v1alpha1" 21 | "k8s.io/apimachinery/pkg/api/errors" 22 | "k8s.io/apimachinery/pkg/labels" 23 | "k8s.io/client-go/tools/cache" 24 | ) 25 | 26 | // GeneralPodAutoscalerLister helps list GeneralPodAutoscalers. 27 | // All objects returned here must be treated as read-only. 28 | type GeneralPodAutoscalerLister interface { 29 | // List lists all GeneralPodAutoscalers in the indexer. 30 | // Objects returned here must be treated as read-only. 31 | List(selector labels.Selector) (ret []*v1alpha1.GeneralPodAutoscaler, err error) 32 | // GeneralPodAutoscalers returns an object that can list and get GeneralPodAutoscalers. 33 | GeneralPodAutoscalers(namespace string) GeneralPodAutoscalerNamespaceLister 34 | GeneralPodAutoscalerListerExpansion 35 | } 36 | 37 | // generalPodAutoscalerLister implements the GeneralPodAutoscalerLister interface. 38 | type generalPodAutoscalerLister struct { 39 | indexer cache.Indexer 40 | } 41 | 42 | // NewGeneralPodAutoscalerLister returns a new GeneralPodAutoscalerLister. 43 | func NewGeneralPodAutoscalerLister(indexer cache.Indexer) GeneralPodAutoscalerLister { 44 | return &generalPodAutoscalerLister{indexer: indexer} 45 | } 46 | 47 | // List lists all GeneralPodAutoscalers in the indexer. 48 | func (s *generalPodAutoscalerLister) List(selector labels.Selector) (ret []*v1alpha1.GeneralPodAutoscaler, err error) { 49 | err = cache.ListAll(s.indexer, selector, func(m interface{}) { 50 | ret = append(ret, m.(*v1alpha1.GeneralPodAutoscaler)) 51 | }) 52 | return ret, err 53 | } 54 | 55 | // GeneralPodAutoscalers returns an object that can list and get GeneralPodAutoscalers. 56 | func (s *generalPodAutoscalerLister) GeneralPodAutoscalers(namespace string) GeneralPodAutoscalerNamespaceLister { 57 | return generalPodAutoscalerNamespaceLister{indexer: s.indexer, namespace: namespace} 58 | } 59 | 60 | // GeneralPodAutoscalerNamespaceLister helps list and get GeneralPodAutoscalers. 61 | // All objects returned here must be treated as read-only. 62 | type GeneralPodAutoscalerNamespaceLister interface { 63 | // List lists all GeneralPodAutoscalers in the indexer for a given namespace. 64 | // Objects returned here must be treated as read-only. 65 | List(selector labels.Selector) (ret []*v1alpha1.GeneralPodAutoscaler, err error) 66 | // Get retrieves the GeneralPodAutoscaler from the indexer for a given namespace and name. 67 | // Objects returned here must be treated as read-only. 68 | Get(name string) (*v1alpha1.GeneralPodAutoscaler, error) 69 | GeneralPodAutoscalerNamespaceListerExpansion 70 | } 71 | 72 | // generalPodAutoscalerNamespaceLister implements the GeneralPodAutoscalerNamespaceLister 73 | // interface. 74 | type generalPodAutoscalerNamespaceLister struct { 75 | indexer cache.Indexer 76 | namespace string 77 | } 78 | 79 | // List lists all GeneralPodAutoscalers in the indexer for a given namespace. 80 | func (s generalPodAutoscalerNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.GeneralPodAutoscaler, err error) { 81 | err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { 82 | ret = append(ret, m.(*v1alpha1.GeneralPodAutoscaler)) 83 | }) 84 | return ret, err 85 | } 86 | 87 | // Get retrieves the GeneralPodAutoscaler from the indexer for a given namespace and name. 88 | func (s generalPodAutoscalerNamespaceLister) Get(name string) (*v1alpha1.GeneralPodAutoscaler, error) { 89 | obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) 90 | if err != nil { 91 | return nil, err 92 | } 93 | if !exists { 94 | return nil, errors.NewNotFound(v1alpha1.Resource("generalpodautoscaler"), name) 95 | } 96 | return obj.(*v1alpha1.GeneralPodAutoscaler), nil 97 | } 98 | -------------------------------------------------------------------------------- /pkg/metrics/interfaces.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // Copyright 2021 The Kubernetes Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package metrics 17 | 18 | import ( 19 | "time" 20 | 21 | autoscaling "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling/v1alpha1" 22 | 23 | "k8s.io/api/core/v1" 24 | "k8s.io/apimachinery/pkg/labels" 25 | ) 26 | 27 | // PodMetric contains pod metric value (the metric values are expected to be the metric as a milli-value) 28 | type PodMetric struct { 29 | Timestamp time.Time 30 | Window time.Duration 31 | Value int64 32 | } 33 | 34 | // PodMetricsInfo contains pod metrics as a map from pod names to PodMetricsInfo 35 | type PodMetricsInfo map[string]PodMetric 36 | 37 | // MetricsClient knows how to query a remote interface to retrieve container-level 38 | // resource metrics as well as pod-level arbitrary metrics 39 | type MetricsClient interface { 40 | // GetResourceMetric gets the given resource metric (and an associated oldest timestamp) 41 | // for all pods matching the specified selector in the given namespace 42 | GetResourceMetric(resource v1.ResourceName, namespace string, selector labels.Selector, container string) (PodMetricsInfo, time.Time, error) 43 | 44 | // GetRawMetric gets the given metric (and an associated oldest timestamp) 45 | // for all pods matching the specified selector in the given namespace 46 | GetRawMetric(metricName string, namespace string, selector labels.Selector, metricSelector labels.Selector) (PodMetricsInfo, time.Time, error) 47 | 48 | // GetObjectMetric gets the given metric (and an associated timestamp) for the given 49 | // object in the given namespace 50 | GetObjectMetric(metricName string, namespace string, objectRef *autoscaling.CrossVersionObjectReference, metricSelector labels.Selector) (int64, time.Time, error) 51 | 52 | // GetExternalMetric gets all the values of a given external metric 53 | // that match the specified selector. 54 | GetExternalMetric(metricName string, namespace string, selector labels.Selector) ([]int64, time.Time, error) 55 | } 56 | -------------------------------------------------------------------------------- /pkg/metrics/legacy_metrics_client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // Copyright 2021 The Kubernetes Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package metrics 17 | 18 | import ( 19 | "context" 20 | "encoding/json" 21 | "fmt" 22 | "strings" 23 | "time" 24 | 25 | v1 "k8s.io/api/core/v1" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | "k8s.io/apimachinery/pkg/labels" 28 | clientset "k8s.io/client-go/kubernetes" 29 | v1core "k8s.io/client-go/kubernetes/typed/core/v1" 30 | heapster "k8s.io/heapster/metrics/api/v1/types" 31 | "k8s.io/klog" 32 | metricsapi "k8s.io/metrics/pkg/apis/metrics/v1alpha1" 33 | 34 | autoscaling "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling/v1alpha1" 35 | ) 36 | 37 | const ( 38 | DefaultHeapsterNamespace = "kube-system" 39 | DefaultHeapsterScheme = "http" 40 | DefaultHeapsterService = "heapster" 41 | DefaultHeapsterPort = "" // use the first exposed port on the service 42 | heapsterDefaultMetricWindow = time.Minute 43 | ) 44 | 45 | var heapsterQueryStart = -5 * time.Minute 46 | 47 | type HeapsterMetricsClient struct { 48 | services v1core.ServiceInterface 49 | podsGetter v1core.PodsGetter 50 | heapsterScheme string 51 | heapsterService string 52 | heapsterPort string 53 | } 54 | 55 | func NewHeapsterMetricsClient(client clientset.Interface, namespace, scheme, service, port string) MetricsClient { 56 | return &HeapsterMetricsClient{ 57 | services: client.CoreV1().Services(namespace), 58 | podsGetter: client.CoreV1(), 59 | heapsterScheme: scheme, 60 | heapsterService: service, 61 | heapsterPort: port, 62 | } 63 | } 64 | 65 | func (h *HeapsterMetricsClient) GetResourceMetric(resource v1.ResourceName, namespace string, selector labels.Selector, container string) (PodMetricsInfo, time.Time, error) { 66 | metricPath := fmt.Sprintf("/apis/metrics/v1alpha1/namespaces/%s/pods", namespace) 67 | params := map[string]string{"labelSelector": selector.String()} 68 | 69 | resultRaw, err := h.services. 70 | ProxyGet(h.heapsterScheme, h.heapsterService, h.heapsterPort, metricPath, params). 71 | DoRaw(context.TODO()) 72 | if err != nil { 73 | return nil, time.Time{}, fmt.Errorf("failed to get pod resource metrics: %v", err) 74 | } 75 | 76 | klog.V(8).Infof("Heapster metrics result: %s", string(resultRaw)) 77 | 78 | metrics := metricsapi.PodMetricsList{} 79 | err = json.Unmarshal(resultRaw, &metrics) 80 | if err != nil { 81 | return nil, time.Time{}, fmt.Errorf("failed to unmarshal heapster response: %v", err) 82 | } 83 | 84 | if len(metrics.Items) == 0 { 85 | return nil, time.Time{}, fmt.Errorf("no metrics returned from heapster") 86 | } 87 | 88 | res := make(PodMetricsInfo, len(metrics.Items)) 89 | 90 | for _, m := range metrics.Items { 91 | podSum := int64(0) 92 | missing := len(m.Containers) == 0 93 | for _, c := range m.Containers { 94 | resValue, found := c.Usage[v1.ResourceName(resource)] 95 | if !found { 96 | missing = true 97 | klog.V(2).Infof("missing resource metric %v for container %s in pod %s/%s", resource, c.Name, namespace, m.Name) 98 | continue 99 | } 100 | podSum += resValue.MilliValue() 101 | } 102 | 103 | if !missing { 104 | res[m.Name] = PodMetric{ 105 | Timestamp: m.Timestamp.Time, 106 | Window: m.Window.Duration, 107 | Value: int64(podSum), 108 | } 109 | } 110 | } 111 | 112 | timestamp := metrics.Items[0].Timestamp.Time 113 | 114 | return res, timestamp, nil 115 | } 116 | 117 | func (h *HeapsterMetricsClient) GetRawMetric(metricName string, namespace string, selector labels.Selector, metricSelector labels.Selector) (PodMetricsInfo, time.Time, error) { 118 | podList, err := h.podsGetter.Pods(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: selector. 119 | String()}) 120 | if err != nil { 121 | return nil, time.Time{}, fmt.Errorf("failed to get pod list while fetching metrics: %v", err) 122 | } 123 | 124 | if len(podList.Items) == 0 { 125 | return nil, time.Time{}, fmt.Errorf("no pods matched the provided selector") 126 | } 127 | 128 | podNames := make([]string, len(podList.Items)) 129 | for i, pod := range podList.Items { 130 | podNames[i] = pod.Name 131 | } 132 | 133 | now := time.Now() 134 | 135 | startTime := now.Add(heapsterQueryStart) 136 | metricPath := fmt.Sprintf("/api/v1/model/namespaces/%s/pod-list/%s/metrics/%s", 137 | namespace, 138 | strings.Join(podNames, ","), 139 | metricName) 140 | 141 | resultRaw, err := h.services. 142 | ProxyGet(h.heapsterScheme, h.heapsterService, h.heapsterPort, metricPath, map[string]string{"start": startTime.Format(time.RFC3339)}). 143 | DoRaw(context.TODO()) 144 | if err != nil { 145 | return nil, time.Time{}, fmt.Errorf("failed to get pod metrics: %v", err) 146 | } 147 | 148 | var metrics heapster.MetricResultList 149 | err = json.Unmarshal(resultRaw, &metrics) 150 | if err != nil { 151 | return nil, time.Time{}, fmt.Errorf("failed to unmarshal heapster response: %v", err) 152 | } 153 | 154 | klog.V(4).Infof("Heapster metrics result: %s", string(resultRaw)) 155 | 156 | if len(metrics.Items) != len(podNames) { 157 | // if we get too many metrics or two few metrics, we have no way of knowing which metric goes to which pod 158 | // (note that Heapster returns *empty* metric items when a pod does not exist or have that metric, so this 159 | // does not cover the "missing metric entry" case) 160 | return nil, time.Time{}, fmt.Errorf("requested metrics for %v pods, got metrics for %v", len(podNames), len(metrics.Items)) 161 | } 162 | 163 | var timestamp *time.Time 164 | res := make(PodMetricsInfo, len(metrics.Items)) 165 | for i, podMetrics := range metrics.Items { 166 | val, podTimestamp, hadMetrics := collapseTimeSamples(podMetrics, time.Minute) 167 | if hadMetrics { 168 | res[podNames[i]] = PodMetric{ 169 | Timestamp: podTimestamp, 170 | Window: heapsterDefaultMetricWindow, 171 | Value: int64(val), 172 | } 173 | 174 | if timestamp == nil || podTimestamp.Before(*timestamp) { 175 | timestamp = &podTimestamp 176 | } 177 | } 178 | } 179 | 180 | if timestamp == nil { 181 | timestamp = &time.Time{} 182 | } 183 | 184 | return res, *timestamp, nil 185 | } 186 | 187 | func (h *HeapsterMetricsClient) GetObjectMetric(metricName string, namespace string, objectRef *autoscaling.CrossVersionObjectReference, metricSelector labels.Selector) (int64, time.Time, error) { 188 | return 0, time.Time{}, fmt.Errorf("object metrics are not yet supported") 189 | } 190 | 191 | func (h *HeapsterMetricsClient) GetExternalMetric(metricName, namespace string, selector labels.Selector) ([]int64, time.Time, error) { 192 | return nil, time.Time{}, fmt.Errorf("external metrics aren't supported") 193 | } 194 | 195 | func collapseTimeSamples(metrics heapster.MetricResult, duration time.Duration) (int64, time.Time, bool) { 196 | floatSum := float64(0) 197 | intSum := int64(0) 198 | intSumCount := 0 199 | floatSumCount := 0 200 | 201 | var newest *heapster.MetricPoint // creation time of the newest sample for this pod 202 | for i, metricPoint := range metrics.Metrics { 203 | if newest == nil || newest.Timestamp.Before(metricPoint.Timestamp) { 204 | newest = &metrics.Metrics[i] 205 | } 206 | } 207 | if newest != nil { 208 | for _, metricPoint := range metrics.Metrics { 209 | if metricPoint.Timestamp.Add(duration).After(newest.Timestamp) { 210 | intSum += int64(metricPoint.Value) 211 | intSumCount++ 212 | if metricPoint.FloatValue != nil { 213 | floatSum += *metricPoint.FloatValue 214 | floatSumCount++ 215 | } 216 | } 217 | } 218 | 219 | if newest.FloatValue != nil { 220 | return int64(floatSum / float64(floatSumCount) * 1000), newest.Timestamp, true 221 | } else { 222 | return (intSum * 1000) / int64(intSumCount), newest.Timestamp, true 223 | } 224 | } 225 | 226 | return 0, time.Time{}, false 227 | } 228 | -------------------------------------------------------------------------------- /pkg/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // Copyright 2021 The Kubernetes Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package metrics 17 | 18 | // Server an HTTP serving instance to track metrics 19 | type Server interface { 20 | NewServer(address string, pattern string) 21 | RecordScalerError(namespace string, scaledObject string, scaler string, scalerIndex int, metric string, err error) 22 | RecordScalerMetric(namespace string, scaledObject string, scaler string, scalerIndex int, metric string, value int64) 23 | RecordScalerObjectError(namespace string, scaledObject string, err error) 24 | } 25 | -------------------------------------------------------------------------------- /pkg/metrics/prometheus_metrics.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // Copyright 2021 The Kubernetes Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package metrics 17 | 18 | import ( 19 | "log" 20 | "net/http" 21 | "strconv" 22 | 23 | "github.com/prometheus/client_golang/prometheus" 24 | "github.com/prometheus/client_golang/prometheus/promhttp" 25 | ) 26 | 27 | var ( 28 | metricLabels = []string{"namespace", "metric", "scaledObject", "scaler", "scalerIndex"} 29 | scalerErrorsTotal = prometheus.NewCounterVec( 30 | prometheus.CounterOpts{ 31 | Namespace: "keda_metrics_adapter", 32 | Subsystem: "scaler", 33 | Name: "errors_total", 34 | Help: "Total number of errors for all scalers", 35 | }, 36 | []string{}, 37 | ) 38 | scalerMetricsValue = prometheus.NewGaugeVec( 39 | prometheus.GaugeOpts{ 40 | Namespace: "keda_metrics_adapter", 41 | Subsystem: "scaler", 42 | Name: "metrics_value", 43 | Help: "Metric Value used for HPA", 44 | }, 45 | metricLabels, 46 | ) 47 | scalerErrors = prometheus.NewCounterVec( 48 | prometheus.CounterOpts{ 49 | Namespace: "keda_metrics_adapter", 50 | Subsystem: "scaler", 51 | Name: "errors", 52 | Help: "Number of scaler errors", 53 | }, 54 | metricLabels, 55 | ) 56 | scaledObjectErrors = prometheus.NewCounterVec( 57 | prometheus.CounterOpts{ 58 | Namespace: "keda_metrics_adapter", 59 | Subsystem: "scaled_object", 60 | Name: "errors", 61 | Help: "Number of scaled object errors", 62 | }, 63 | []string{"namespace", "scaledObject"}, 64 | ) 65 | ) 66 | 67 | // PrometheusMetricServer the type of MetricsServer 68 | type PrometheusMetricServer struct{} 69 | 70 | var registry *prometheus.Registry 71 | 72 | func init() { 73 | registry = prometheus.NewRegistry() 74 | registry.MustRegister(scalerErrorsTotal) 75 | registry.MustRegister(scalerMetricsValue) 76 | registry.MustRegister(scalerErrors) 77 | registry.MustRegister(scaledObjectErrors) 78 | } 79 | 80 | // NewServer creates a new http serving instance of prometheus metrics 81 | func (metricsServer PrometheusMetricServer) NewServer(address string, pattern string) { 82 | http.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { 83 | w.WriteHeader(http.StatusOK) 84 | _, err := w.Write([]byte("OK")) 85 | if err != nil { 86 | log.Fatalf("Unable to write to serve custom metrics: %v", err) 87 | } 88 | }) 89 | log.Printf("Starting metrics server at %v", address) 90 | http.Handle(pattern, promhttp.HandlerFor(registry, promhttp.HandlerOpts{})) 91 | 92 | // initialize the total error metric 93 | _, errscaler := scalerErrorsTotal.GetMetricWith(prometheus.Labels{}) 94 | if errscaler != nil { 95 | log.Fatalf("Unable to initialize total error metrics as : %v", errscaler) 96 | } 97 | 98 | log.Fatal(http.ListenAndServe(address, nil)) 99 | } 100 | 101 | // RecordHPAScalerMetric create a measurement of the external metric used by the HPA 102 | func (metricsServer PrometheusMetricServer) RecordHPAScalerMetric(namespace string, scaledObject string, scaler string, scalerIndex int, metric string, value int64) { 103 | scalerMetricsValue.With(getLabels(namespace, scaledObject, scaler, scalerIndex, metric)).Set(float64(value)) 104 | } 105 | 106 | // RecordHPAScalerError counts the number of errors occurred in trying get an external metric used by the HPA 107 | func (metricsServer PrometheusMetricServer) RecordHPAScalerError(namespace string, scaledObject string, scaler string, scalerIndex int, metric string, err error) { 108 | if err != nil { 109 | scalerErrors.With(getLabels(namespace, scaledObject, scaler, scalerIndex, metric)).Inc() 110 | // scaledObjectErrors.With(prometheus.Labels{"namespace": namespace, "scaledObject": scaledObject}).Inc() 111 | metricsServer.RecordScalerObjectError(namespace, scaledObject, err) 112 | scalerErrorsTotal.With(prometheus.Labels{}).Inc() 113 | return 114 | } 115 | // initialize metric with 0 if not already set 116 | _, errscaler := scalerErrors.GetMetricWith(getLabels(namespace, scaledObject, scaler, scalerIndex, metric)) 117 | if errscaler != nil { 118 | log.Fatalf("Unable to write to serve custom metrics: %v", errscaler) 119 | } 120 | } 121 | 122 | // RecordScalerObjectError counts the number of errors with the scaled object 123 | func (metricsServer PrometheusMetricServer) RecordScalerObjectError(namespace string, scaledObject string, err error) { 124 | labels := prometheus.Labels{"namespace": namespace, "scaledObject": scaledObject} 125 | if err != nil { 126 | scaledObjectErrors.With(labels).Inc() 127 | return 128 | } 129 | // initialize metric with 0 if not already set 130 | _, errscaledobject := scaledObjectErrors.GetMetricWith(labels) 131 | if errscaledobject != nil { 132 | log.Fatalf("Unable to write to serve custom metrics: %v", errscaledobject) 133 | return 134 | } 135 | } 136 | 137 | func getLabels(namespace string, scaledObject string, scaler string, scalerIndex int, metric string) prometheus.Labels { 138 | return prometheus.Labels{"namespace": namespace, "scaledObject": scaledObject, "scaler": scaler, "scalerIndex": strconv.Itoa(scalerIndex), "metric": metric} 139 | } 140 | -------------------------------------------------------------------------------- /pkg/metrics/rest_metrics_client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // Copyright 2021 The Kubernetes Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package metrics 17 | 18 | import ( 19 | "context" 20 | "fmt" 21 | "time" 22 | 23 | "k8s.io/api/core/v1" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | "k8s.io/apimachinery/pkg/labels" 26 | "k8s.io/apimachinery/pkg/runtime/schema" 27 | "k8s.io/klog" 28 | customapi "k8s.io/metrics/pkg/apis/custom_metrics/v1beta2" 29 | "k8s.io/metrics/pkg/apis/metrics/v1beta1" 30 | resourceclient "k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1beta1" 31 | customclient "k8s.io/metrics/pkg/client/custom_metrics" 32 | externalclient "k8s.io/metrics/pkg/client/external_metrics" 33 | 34 | autoscaling "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling/v1alpha1" 35 | ) 36 | 37 | const ( 38 | metricServerDefaultMetricWindow = time.Minute 39 | ) 40 | 41 | func NewRESTMetricsClient(resourceClient resourceclient.PodMetricsesGetter, customClient customclient.CustomMetricsClient, externalClient externalclient.ExternalMetricsClient) MetricsClient { 42 | return &restMetricsClient{ 43 | &resourceMetricsClient{resourceClient}, 44 | &customMetricsClient{customClient}, 45 | &externalMetricsClient{externalClient}, 46 | } 47 | } 48 | 49 | // restMetricsClient is a client which supports fetching 50 | // metrics from both the resource metrics API and the 51 | // custom metrics API. 52 | type restMetricsClient struct { 53 | *resourceMetricsClient 54 | *customMetricsClient 55 | *externalMetricsClient 56 | } 57 | 58 | // resourceMetricsClient implements the resource-metrics-related parts of MetricsClient, 59 | // using data from the resource metrics API. 60 | type resourceMetricsClient struct { 61 | client resourceclient.PodMetricsesGetter 62 | } 63 | 64 | // GetResourceMetric gets the given resource metric (and an associated oldest timestamp) 65 | // for all pods matching the specified selector in the given namespace 66 | func (c *resourceMetricsClient) GetResourceMetric(resource v1.ResourceName, namespace string, selector labels.Selector, container string) (PodMetricsInfo, time.Time, error) { 67 | metrics, err := c.client.PodMetricses(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: selector. 68 | String()}) 69 | if err != nil { 70 | return nil, time.Time{}, fmt.Errorf("unable to fetch metrics from resource metrics API: %v", err) 71 | } 72 | 73 | if len(metrics.Items) == 0 { 74 | return nil, time.Time{}, fmt.Errorf("no metrics returned from resource metrics API") 75 | } 76 | var res PodMetricsInfo 77 | if container != "" { 78 | res, err = getContainerMetrics(metrics.Items, resource, container) 79 | if err != nil { 80 | return nil, time.Time{}, fmt.Errorf("failed to get container metrics: %v", err) 81 | } 82 | } else { 83 | res = getPodMetrics(metrics.Items, resource) 84 | } 85 | 86 | timestamp := metrics.Items[0].Timestamp.Time 87 | return res, timestamp, nil 88 | } 89 | 90 | func getContainerMetrics(rawMetrics []v1beta1.PodMetrics, resource v1.ResourceName, container string) (PodMetricsInfo, error) { 91 | res := make(PodMetricsInfo, len(rawMetrics)) 92 | for _, m := range rawMetrics { 93 | containerFound := false 94 | for _, c := range m.Containers { 95 | if c.Name != container { 96 | continue 97 | } 98 | containerFound = true 99 | if val, resFound := c.Usage[resource]; resFound { 100 | res[m.Name] = PodMetric{ 101 | Timestamp: m.Timestamp.Time, 102 | Window: m.Window.Duration, 103 | Value: val.MilliValue(), 104 | } 105 | } 106 | break 107 | } 108 | if !containerFound { 109 | return nil, fmt.Errorf("container %s not present in metrics for pod %s/%s", container, m.Namespace, m.Name) 110 | } 111 | } 112 | return res, nil 113 | } 114 | 115 | func getPodMetrics(rawMetrics []v1beta1.PodMetrics, resource v1.ResourceName) PodMetricsInfo { 116 | res := make(PodMetricsInfo, len(rawMetrics)) 117 | for _, m := range rawMetrics { 118 | podSum := int64(0) 119 | missing := len(m.Containers) == 0 120 | for _, c := range m.Containers { 121 | resValue, found := c.Usage[resource] 122 | if !found { 123 | missing = true 124 | klog.V(2).Infof("missing resource metric %v for %s/%s", resource, m.Namespace, m.Name) 125 | break 126 | } 127 | podSum += resValue.MilliValue() 128 | } 129 | if !missing { 130 | res[m.Name] = PodMetric{ 131 | Timestamp: m.Timestamp.Time, 132 | Window: m.Window.Duration, 133 | Value: podSum, 134 | } 135 | } 136 | } 137 | return res 138 | } 139 | 140 | // customMetricsClient implements the custom-metrics-related parts of MetricsClient, 141 | // using data from the custom metrics API. 142 | type customMetricsClient struct { 143 | client customclient.CustomMetricsClient 144 | } 145 | 146 | // GetRawMetric gets the given metric (and an associated oldest timestamp) 147 | // for all pods matching the specified selector in the given namespace 148 | func (c *customMetricsClient) GetRawMetric(metricName string, namespace string, selector labels.Selector, metricSelector labels.Selector) (PodMetricsInfo, time.Time, error) { 149 | metrics, err := c.client.NamespacedMetrics(namespace).GetForObjects(schema.GroupKind{Kind: "Pod"}, selector, metricName, metricSelector) 150 | if err != nil { 151 | return nil, time.Time{}, fmt.Errorf("unable to fetch metrics from custom metrics API: %v", err) 152 | } 153 | 154 | if len(metrics.Items) == 0 { 155 | return nil, time.Time{}, fmt.Errorf("no metrics returned from custom metrics API") 156 | } 157 | 158 | res := make(PodMetricsInfo, len(metrics.Items)) 159 | for _, m := range metrics.Items { 160 | window := metricServerDefaultMetricWindow 161 | if m.WindowSeconds != nil { 162 | window = time.Duration(*m.WindowSeconds) * time.Second 163 | } 164 | res[m.DescribedObject.Name] = PodMetric{ 165 | Timestamp: m.Timestamp.Time, 166 | Window: window, 167 | Value: int64(m.Value.MilliValue()), 168 | } 169 | 170 | m.Value.MilliValue() 171 | } 172 | 173 | timestamp := metrics.Items[0].Timestamp.Time 174 | 175 | return res, timestamp, nil 176 | } 177 | 178 | // GetObjectMetric gets the given metric (and an associated timestamp) for the given 179 | // object in the given namespace 180 | func (c *customMetricsClient) GetObjectMetric(metricName string, namespace string, objectRef *autoscaling.CrossVersionObjectReference, metricSelector labels.Selector) (int64, time.Time, error) { 181 | gvk := schema.FromAPIVersionAndKind(objectRef.APIVersion, objectRef.Kind) 182 | var metricValue *customapi.MetricValue 183 | var err error 184 | if gvk.Kind == "Namespace" && gvk.Group == "" { 185 | // handle namespace separately 186 | // NB: we ignore namespace name here, since CrossVersionObjectReference isn't 187 | // supposed to allow you to escape your namespace 188 | metricValue, err = c.client.RootScopedMetrics().GetForObject(gvk.GroupKind(), namespace, metricName, metricSelector) 189 | } else { 190 | metricValue, err = c.client.NamespacedMetrics(namespace).GetForObject(gvk.GroupKind(), objectRef.Name, metricName, metricSelector) 191 | } 192 | 193 | if err != nil { 194 | return 0, time.Time{}, fmt.Errorf("unable to fetch metrics from custom metrics API: %v", err) 195 | } 196 | 197 | return metricValue.Value.MilliValue(), metricValue.Timestamp.Time, nil 198 | } 199 | 200 | // externalMetricsClient implements the external metrics related parts of MetricsClient, 201 | // using data from the external metrics API. 202 | type externalMetricsClient struct { 203 | client externalclient.ExternalMetricsClient 204 | } 205 | 206 | // GetExternalMetric gets all the values of a given external metric 207 | // that match the specified selector. 208 | func (c *externalMetricsClient) GetExternalMetric(metricName, namespace string, selector labels.Selector) ([]int64, time.Time, error) { 209 | metrics, err := c.client.NamespacedMetrics(namespace).List(metricName, selector) 210 | if err != nil { 211 | return []int64{}, time.Time{}, fmt.Errorf("unable to fetch metrics from external metrics API: %v", err) 212 | } 213 | 214 | if len(metrics.Items) == 0 { 215 | return nil, time.Time{}, fmt.Errorf("no metrics returned from external metrics API") 216 | } 217 | 218 | res := make([]int64, 0) 219 | for _, m := range metrics.Items { 220 | res = append(res, m.Value.MilliValue()) 221 | } 222 | timestamp := metrics.Items[0].Timestamp.Time 223 | return res, timestamp, nil 224 | } 225 | -------------------------------------------------------------------------------- /pkg/metrics/utilization.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // Copyright 2021 The Kubernetes Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package metrics 17 | 18 | import ( 19 | "fmt" 20 | ) 21 | 22 | // GetResourceUtilizationRatio takes in a set of metrics, a set of matching requests, 23 | // and a target utilization percentage, and calculates the ratio of 24 | // desired to actual utilization (returning that, the actual utilization, and the raw average value) 25 | func GetResourceUtilizationRatio(metrics PodMetricsInfo, requests map[string]int64, targetUtilization int32) (utilizationRatio float64, currentUtilization int32, rawAverageValue int64, err error) { 26 | metricsTotal := int64(0) 27 | requestsTotal := int64(0) 28 | numEntries := 0 29 | 30 | for podName, metric := range metrics { 31 | request, hasRequest := requests[podName] 32 | if !hasRequest { 33 | // we check for missing requests elsewhere, so assuming missing requests == extraneous metrics 34 | continue 35 | } 36 | 37 | metricsTotal += metric.Value 38 | requestsTotal += request 39 | numEntries++ 40 | } 41 | 42 | // if the set of requests is completely disjoint from the set of metrics, 43 | // then we could have an issue where the requests total is zero 44 | if requestsTotal == 0 { 45 | return 0, 0, 0, fmt.Errorf("no metrics returned matched known pods") 46 | } 47 | 48 | currentUtilization = int32((metricsTotal * 100) / requestsTotal) 49 | 50 | return float64(currentUtilization) / float64(targetUtilization), currentUtilization, metricsTotal / int64(numEntries), nil 51 | } 52 | 53 | // GetMetricUtilizationRatio takes in a set of metrics and a target utilization value, 54 | // and calculates the ratio of desired to actual utilization 55 | // (returning that and the actual utilization) 56 | func GetMetricUtilizationRatio(metrics PodMetricsInfo, targetUtilization int64) (utilizationRatio float64, currentUtilization int64) { 57 | metricsTotal := int64(0) 58 | for _, metric := range metrics { 59 | metricsTotal += metric.Value 60 | } 61 | 62 | currentUtilization = metricsTotal / int64(len(metrics)) 63 | 64 | return float64(currentUtilization) / float64(targetUtilization), currentUtilization 65 | } 66 | -------------------------------------------------------------------------------- /pkg/requests/api.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package requests 16 | 17 | import "k8s.io/apimachinery/pkg/types" 18 | 19 | // AutoscaleRequest defines the request to webhook autoscaler endpoint 20 | type AutoscaleRequest struct { 21 | // UID is used for tracing the request and response. 22 | UID types.UID `json:"uid"` 23 | // Name is the name of the workload(Squad, Statefulset...) being scaled 24 | Name string `json:"name"` 25 | // Namespace is the workload namespace 26 | Namespace string `json:"namespace"` 27 | // Parameters are the parameter that required by webhook 28 | Parameters map[string]string `json:"parameters"` 29 | // CurrentReplicas is the current replicas 30 | CurrentReplicas int32 `json:"currentReplicas"` 31 | } 32 | 33 | // AutoscaleResponse defines the response of webhook server 34 | type AutoscaleResponse struct { 35 | // UID is used for tracing the request and response. 36 | // It should be same as it in the request. 37 | UID types.UID `json:"uid"` 38 | // Set to false if should not do scaling 39 | Scale bool `json:"scale"` 40 | // Replicas is targeted replica count from the webhookServer 41 | Replicas int32 `json:"replicas"` 42 | } 43 | 44 | // AutoscaleReview is passed to the webhook with a populated Request value, 45 | // and then returned with a populated Response. 46 | type AutoscaleReview struct { 47 | Request *AutoscaleRequest `json:"request"` 48 | Response *AutoscaleResponse `json:"response"` 49 | } 50 | -------------------------------------------------------------------------------- /pkg/scaler/rate_limiters.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // Copyright 2021 The Kubernetes Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package scaler 17 | 18 | import ( 19 | "time" 20 | 21 | "k8s.io/client-go/util/workqueue" 22 | ) 23 | 24 | // FixedItemIntervalRateLimiter limits items to a fixed-rate interval 25 | type FixedItemIntervalRateLimiter struct { 26 | interval time.Duration 27 | } 28 | 29 | var _ workqueue.RateLimiter = &FixedItemIntervalRateLimiter{} 30 | 31 | // NewFixedItemIntervalRateLimiter creates a new instance of a RateLimiter using a fixed interval 32 | func NewFixedItemIntervalRateLimiter(interval time.Duration) workqueue.RateLimiter { 33 | return &FixedItemIntervalRateLimiter{ 34 | interval: interval, 35 | } 36 | } 37 | 38 | // When returns the interval of the rate limiter 39 | func (r *FixedItemIntervalRateLimiter) When(item interface{}) time.Duration { 40 | return r.interval 41 | } 42 | 43 | // NumRequeues returns back how many failures the item has had 44 | func (r *FixedItemIntervalRateLimiter) NumRequeues(item interface{}) int { 45 | return 1 46 | } 47 | 48 | // Forget indicates that an item is finished being retried. 49 | func (r *FixedItemIntervalRateLimiter) Forget(item interface{}) { 50 | } 51 | 52 | // NewDefaultGPARateLimiter creates a rate limiter which limits overall (as per the 53 | // default controller rate limiter), as well as per the resync interval 54 | func NewDefaultGPARateLimiter(interval time.Duration) workqueue.RateLimiter { 55 | return NewFixedItemIntervalRateLimiter(interval) 56 | } 57 | -------------------------------------------------------------------------------- /pkg/scaler/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // Copyright 2021 The Kubernetes Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package scaler 17 | 18 | import ( 19 | "encoding/json" 20 | 21 | v1 "k8s.io/api/core/v1" 22 | "k8s.io/apimachinery/pkg/util/strategicpatch" 23 | ) 24 | 25 | // GetPodCondition extracts the provided condition from the given status and returns that. 26 | // Returns nil and -1 if the condition is not present, and the index of the located condition. 27 | func GetPodCondition(status *v1.PodStatus, conditionType v1.PodConditionType) (int, *v1.PodCondition) { 28 | if status == nil { 29 | return -1, nil 30 | } 31 | return GetPodConditionFromList(status.Conditions, conditionType) 32 | } 33 | 34 | // GetPodConditionFromList extracts the provided condition from the given list of condition and 35 | // returns the index of the condition and the condition. Returns -1 and nil if the condition is not present. 36 | func GetPodConditionFromList(conditions []v1.PodCondition, conditionType v1.PodConditionType) (int, *v1.PodCondition) { 37 | if conditions == nil { 38 | return -1, nil 39 | } 40 | for i := range conditions { 41 | if conditions[i].Type == conditionType { 42 | return i, &conditions[i] 43 | } 44 | } 45 | return -1, nil 46 | } 47 | 48 | // IsPodReady returns true if a pod is ready; false otherwise. 49 | func IsPodReady(pod *v1.Pod) bool { 50 | return IsPodReadyConditionTrue(pod.Status) 51 | } 52 | 53 | // IsPodReadyConditionTrue returns true if a pod is ready; false otherwise. 54 | func IsPodReadyConditionTrue(status v1.PodStatus) bool { 55 | condition := GetPodReadyCondition(status) 56 | return condition != nil && condition.Status == v1.ConditionTrue 57 | } 58 | 59 | // GetPodReadyCondition extracts the pod ready condition from the given status and returns that. 60 | // Returns nil if the condition is not present. 61 | func GetPodReadyCondition(status v1.PodStatus) *v1.PodCondition { 62 | _, condition := GetPodCondition(&status, v1.PodReady) 63 | return condition 64 | } 65 | 66 | // CreateMergePatch return patch generated from original and new interfaces 67 | func CreateMergePatch(original, new interface{}) ([]byte, error) { 68 | pvByte, err := json.Marshal(original) 69 | if err != nil { 70 | return nil, err 71 | } 72 | cloneByte, err := json.Marshal(new) 73 | if err != nil { 74 | return nil, err 75 | } 76 | patch, err := strategicpatch.CreateTwoWayMergePatch(pvByte, cloneByte, original) 77 | if err != nil { 78 | return nil, err 79 | } 80 | return patch, nil 81 | } 82 | -------------------------------------------------------------------------------- /pkg/scalercore/cron.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package scalercore 16 | 17 | import ( 18 | "time" 19 | 20 | "github.com/robfig/cron" 21 | "k8s.io/klog" 22 | 23 | "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling/v1alpha1" 24 | ) 25 | 26 | var _ Scaler = &CronScaler{} 27 | var recordScheduleName = "" 28 | 29 | // CronScaler is a crontab GPA 30 | type CronScaler struct { 31 | ranges []v1alpha1.TimeRange 32 | name string 33 | now time.Time 34 | } 35 | 36 | // NewCronScaler initializer crontab GPA 37 | func NewCronScaler(ranges []v1alpha1.TimeRange) Scaler { 38 | return &CronScaler{ranges: ranges, name: Cron, now: time.Now()} 39 | } 40 | 41 | // GetReplicas return replicas recommend by crontab GPA 42 | func (s *CronScaler) GetReplicas(gpa *v1alpha1.GeneralPodAutoscaler, currentReplicas int32) (int32, error) { 43 | var max int32 = 0 44 | for _, t := range s.ranges { 45 | misMatch, finalMatch, err := s.getFinalMatchAndMisMatch(gpa, t.Schedule) 46 | if err != nil { 47 | klog.Error(err) 48 | return currentReplicas, nil 49 | } 50 | klog.Infof("firstMisMatch: %v, finalMatch: %v", misMatch, finalMatch) 51 | if finalMatch == nil { 52 | continue 53 | } 54 | if max < t.DesiredReplicas { 55 | max = t.DesiredReplicas 56 | recordScheduleName = t.Schedule 57 | } 58 | klog.Infof("Schedule %v recommend %v replicas, desire: %v", t.Schedule, max, t.DesiredReplicas) 59 | } 60 | if max == 0 { 61 | klog.Info("Recommend 0 replicas, use current replicas number") 62 | max = gpa.Status.DesiredReplicas 63 | } 64 | return max, nil 65 | } 66 | 67 | // ScalerName returns scaler name 68 | func (s *CronScaler) ScalerName() string { 69 | return s.name 70 | } 71 | 72 | func (s *CronScaler) getFinalMatchAndMisMatch(gpa *v1alpha1.GeneralPodAutoscaler, schedule string) (*time.Time, *time.Time, error) { 73 | sched, err := cron.ParseStandard(schedule) 74 | if err != nil { 75 | return nil, nil, err 76 | } 77 | lastTime := gpa.Status.LastCronScheduleTime.DeepCopy() 78 | if recordScheduleName != schedule { 79 | lastTime = nil 80 | } 81 | if lastTime == nil || lastTime.IsZero() { 82 | lastTime = gpa.CreationTimestamp.DeepCopy() 83 | } 84 | match := lastTime.Time 85 | misMatch := lastTime.Time 86 | klog.Infof("Init time: %v, now: %v", lastTime, s.now) 87 | t := lastTime.Time 88 | for { 89 | if !t.After(s.now) { 90 | misMatch = t 91 | t = sched.Next(t) 92 | continue 93 | } 94 | match = t 95 | break 96 | } 97 | if s.now.Sub(misMatch).Minutes() < 1 && s.now.After(misMatch) { 98 | return &misMatch, &match, nil 99 | } 100 | 101 | return nil, nil, nil 102 | } 103 | -------------------------------------------------------------------------------- /pkg/scalercore/cron_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package scalercore 16 | 17 | import ( 18 | "testing" 19 | "time" 20 | 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | 23 | "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling/v1alpha1" 24 | ) 25 | 26 | func Test_GetReplicas(t *testing.T) { 27 | testTime1, err := time.Parse("2006-01-02 15:04:05", "2020-12-18 09:04:41") 28 | testTime2, err := time.Parse("2006-01-02 15:04:05", "2020-12-18 09:00:01") 29 | testTime3, err := time.Parse("2006-01-02 15:04:05", "2020-12-18 12:59:59") 30 | testTime4, err := time.Parse("2006-01-02 15:04:05", "2020-12-18 12:58:59") 31 | testTime5, err := time.Parse("2006-01-02 15:04:05", "2020-12-18 08:58:59") 32 | lastTime := metav1.Time{Time: testTime1.Add(-1 * time.Second)} 33 | gpa := &v1alpha1.GeneralPodAutoscaler{ 34 | ObjectMeta: metav1.ObjectMeta{ 35 | CreationTimestamp: metav1.Time{Time: testTime1.Add(-60 * time.Minute)}, 36 | }, 37 | Status: v1alpha1.GeneralPodAutoscalerStatus{}, 38 | } 39 | definedGPA := &v1alpha1.GeneralPodAutoscaler{ 40 | ObjectMeta: metav1.ObjectMeta{ 41 | CreationTimestamp: metav1.Time{Time: testTime1.Add(-60 * time.Minute)}, 42 | }, 43 | Status: v1alpha1.GeneralPodAutoscalerStatus{ 44 | LastCronScheduleTime: &lastTime, 45 | }, 46 | } 47 | cronGPA := gpa.DeepCopy() 48 | lastCronTime := metav1.Time{Time: testTime5} 49 | cronGPA.Status.LastCronScheduleTime = &lastCronTime 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | for _, c := range []struct { 54 | name string 55 | ranges []v1alpha1.TimeRange 56 | desired int32 57 | gpa *v1alpha1.GeneralPodAutoscaler 58 | time time.Time 59 | }{ 60 | { 61 | name: "single timeRange, out of range", 62 | ranges: []v1alpha1.TimeRange{ 63 | { 64 | Schedule: "*/1 10-12 * * *", 65 | DesiredReplicas: 1, 66 | }, 67 | }, 68 | desired: 0, 69 | }, 70 | { 71 | name: "single timeRange, in range", 72 | ranges: []v1alpha1.TimeRange{ 73 | { 74 | Schedule: "*/1 9-12 * * *", 75 | DesiredReplicas: 1, 76 | }, 77 | }, 78 | desired: 1, 79 | }, 80 | { 81 | name: "multi timeRange, none match", 82 | ranges: []v1alpha1.TimeRange{ 83 | { 84 | Schedule: "*/1 10-12 * * *", 85 | DesiredReplicas: 1, 86 | }, 87 | { 88 | Schedule: "*/1 13-16 * * *", 89 | DesiredReplicas: 1, 90 | }, 91 | }, 92 | desired: 0, 93 | }, 94 | { 95 | name: "multi timeRange, one match", 96 | ranges: []v1alpha1.TimeRange{ 97 | { 98 | Schedule: "*/1 9-12 * * *", 99 | DesiredReplicas: 1, 100 | }, 101 | { 102 | Schedule: "*/1 13-16 * * *", 103 | DesiredReplicas: 3, 104 | }, 105 | }, 106 | desired: 1, 107 | }, 108 | { 109 | name: "multi timeRange, all match", 110 | ranges: []v1alpha1.TimeRange{ 111 | { 112 | Schedule: "*/1 8-12 * * *", 113 | DesiredReplicas: 1, 114 | }, 115 | { 116 | Schedule: "*/1 9-10 * * *", 117 | DesiredReplicas: 3, 118 | }, 119 | }, 120 | desired: 3, 121 | }, 122 | { 123 | name: "cross day, not match", 124 | ranges: []v1alpha1.TimeRange{ 125 | { 126 | Schedule: "*/1 1-3 * * *", 127 | DesiredReplicas: 1, 128 | }, 129 | }, 130 | desired: 0, 131 | }, 132 | { 133 | name: "single timeRange, in range, in a minute", 134 | ranges: []v1alpha1.TimeRange{ 135 | { 136 | Schedule: "*/1 9-12 * * *", 137 | DesiredReplicas: 1, 138 | }, 139 | }, 140 | desired: 1, 141 | gpa: definedGPA, 142 | }, 143 | { 144 | name: "single timeRange, in range, in a minute", 145 | ranges: []v1alpha1.TimeRange{ 146 | { 147 | Schedule: "*/1 9-12 * * *", 148 | DesiredReplicas: 1, 149 | }, 150 | }, 151 | desired: 1, 152 | gpa: definedGPA, 153 | }, 154 | { 155 | name: "single timeRange, in range, start boundary", 156 | ranges: []v1alpha1.TimeRange{ 157 | { 158 | Schedule: "*/1 9-12 * * *", 159 | DesiredReplicas: 1, 160 | }, 161 | }, 162 | desired: 1, 163 | time: testTime2, 164 | }, 165 | { 166 | name: "single timeRange, in range, end boundary", 167 | ranges: []v1alpha1.TimeRange{ 168 | { 169 | Schedule: "*/1 9-12 * * *", 170 | DesiredReplicas: 1, 171 | }, 172 | }, 173 | desired: 1, 174 | time: testTime3, 175 | }, 176 | { 177 | name: "single timeRange, in range, end boundary, 1 minute left", 178 | ranges: []v1alpha1.TimeRange{ 179 | { 180 | Schedule: "*/1 9-12 * * *", 181 | DesiredReplicas: 1, 182 | }, 183 | }, 184 | desired: 1, 185 | time: testTime4, 186 | }, 187 | { 188 | name: "multi timeRange lastCronTime is not nil, lastCron out this range, in range, end boundary, 1 minute left", 189 | ranges: []v1alpha1.TimeRange{ 190 | { 191 | Schedule: "*/1 9-12 * * *", 192 | DesiredReplicas: 1, 193 | }, 194 | { 195 | Schedule: "*/1 5-8 * * *", 196 | DesiredReplicas: 2, 197 | }, 198 | }, 199 | desired: 1, 200 | time: testTime4, 201 | gpa: cronGPA, 202 | }, 203 | } { 204 | t.Run(c.name, func(t *testing.T) { 205 | defaultGPA := gpa 206 | if c.gpa != nil { 207 | defaultGPA = c.gpa 208 | } 209 | testTime := testTime1 210 | if !c.time.IsZero() { 211 | testTime = c.time 212 | } 213 | cron := &CronScaler{ranges: c.ranges, name: Cron, now: testTime} 214 | actual, err := cron.GetReplicas(defaultGPA, 0) 215 | if err != nil { 216 | t.Error(err) 217 | } 218 | if actual != c.desired { 219 | t.Errorf("desired: %v, actual: %v", c.desired, actual) 220 | } 221 | }) 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /pkg/scalercore/event.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package scalercore 16 | 17 | import autoscalingv1 "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling/v1alpha1" 18 | 19 | var _ Scaler = &EventScaler{} 20 | 21 | type EventScaler struct { 22 | schedule string 23 | name string 24 | } 25 | 26 | func NewEventScaler(schedule string) Scaler { 27 | return &EventScaler{schedule: schedule, name: Event} 28 | } 29 | 30 | func (e *EventScaler) Run(stopCh <-chan struct{}) error { 31 | return nil 32 | } 33 | 34 | func (e *EventScaler) GetReplicas(*autoscalingv1.GeneralPodAutoscaler, int32) (int32, error) { 35 | return 0, nil 36 | } 37 | 38 | func (s *EventScaler) ScalerName() string { 39 | return s.name 40 | } 41 | -------------------------------------------------------------------------------- /pkg/scalercore/scaler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package scalercore 16 | 17 | import autoscalingv1 "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling/v1alpha1" 18 | 19 | type ScalerName string 20 | 21 | const ( 22 | Webhook = "Webhook" 23 | Event = "Event" 24 | Cron = "Cron" 25 | ) 26 | 27 | type Scaler interface { 28 | GetReplicas(*autoscalingv1.GeneralPodAutoscaler, int32) (int32, error) 29 | ScalerName() string 30 | } 31 | 32 | type LongRunScaler interface { 33 | Scaler 34 | Run(<-chan struct{}) error 35 | } 36 | -------------------------------------------------------------------------------- /pkg/scalercore/webhook.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package scalercore 16 | 17 | import ( 18 | "crypto/tls" 19 | "crypto/x509" 20 | "encoding/json" 21 | "fmt" 22 | "io/ioutil" 23 | "net/http" 24 | "net/url" 25 | "strings" 26 | "time" 27 | 28 | "github.com/pkg/errors" 29 | "k8s.io/apimachinery/pkg/util/uuid" 30 | 31 | autoscalingv1 "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling/v1alpha1" 32 | "github.com/ocgi/general-pod-autoscaler/pkg/requests" 33 | ) 34 | 35 | var client = http.Client{ 36 | Timeout: 15 * time.Second, 37 | } 38 | 39 | var _ Scaler = &WebhookScaler{} 40 | 41 | type WebhookScaler struct { 42 | modeConfig *autoscalingv1.WebhookMode 43 | name string 44 | } 45 | 46 | func NewWebhookScaler(modeConfig *autoscalingv1.WebhookMode) Scaler { 47 | return &WebhookScaler{modeConfig: modeConfig, name: Webhook} 48 | } 49 | 50 | func (s *WebhookScaler) GetReplicas(gpa *autoscalingv1.GeneralPodAutoscaler, currentReplicas int32) (int32, error) { 51 | if s.modeConfig == nil { 52 | return 0, errors.New("webhookPolicy parameter must not be nil") 53 | } 54 | 55 | u, err := s.buildURLFromWebhookPolicy() 56 | if err != nil { 57 | return 0, err 58 | } 59 | req := requests.AutoscaleReview{ 60 | Request: &requests.AutoscaleRequest{ 61 | UID: uuid.NewUUID(), 62 | Name: gpa.Spec.ScaleTargetRef.Name, 63 | // gpa and workload must deploy in the same namespace 64 | Namespace: gpa.Namespace, 65 | Parameters: s.modeConfig.Parameters, 66 | CurrentReplicas: currentReplicas, 67 | }, 68 | Response: nil, 69 | } 70 | 71 | b, err := json.Marshal(req) 72 | if err != nil { 73 | return 0, err 74 | } 75 | 76 | res, err := client.Post( 77 | u.String(), 78 | "application/json", 79 | strings.NewReader(string(b)), 80 | ) 81 | if err != nil { 82 | return 0, err 83 | } 84 | defer func() { 85 | if cerr := res.Body.Close(); cerr != nil { 86 | if err != nil { 87 | err = errors.Wrap(err, cerr.Error()) 88 | } else { 89 | err = cerr 90 | } 91 | } 92 | }() 93 | 94 | if res.StatusCode != http.StatusOK { 95 | return 0, fmt.Errorf("bad status code %d from the server: %s", res.StatusCode, u.String()) 96 | } 97 | result, err := ioutil.ReadAll(res.Body) 98 | if err != nil { 99 | return 0, err 100 | } 101 | 102 | var faResp requests.AutoscaleReview 103 | err = json.Unmarshal(result, &faResp) 104 | if err != nil { 105 | return 0, err 106 | } 107 | if faResp.Response == nil { 108 | return 0, fmt.Errorf("received empty reponse") 109 | } 110 | if faResp.Response.Scale { 111 | return faResp.Response.Replicas, nil 112 | } 113 | return currentReplicas, nil 114 | 115 | } 116 | 117 | func (s *WebhookScaler) ScalerName() string { 118 | return s.name 119 | } 120 | 121 | // buildURLFromWebhookPolicy - build URL for Webhook and set CARoot for client Transport 122 | func (s *WebhookScaler) buildURLFromWebhookPolicy() (u *url.URL, err error) { 123 | w := s.modeConfig 124 | if w.URL != nil && w.Service != nil { 125 | return nil, errors.New("service and URL cannot be used simultaneously") 126 | } 127 | 128 | scheme := "http" 129 | if w.CABundle != nil { 130 | scheme = "https" 131 | 132 | if err := setCABundle(w.CABundle); err != nil { 133 | return nil, err 134 | } 135 | } 136 | 137 | if w.URL != nil { 138 | if *w.URL == "" { 139 | return nil, errors.New("URL was not provided") 140 | } 141 | 142 | return url.ParseRequestURI(*w.URL) 143 | } 144 | 145 | if w.Service == nil { 146 | return nil, errors.New("service was not provided, either URL or Service must be provided") 147 | } 148 | 149 | if w.Service.Name == "" { 150 | return nil, errors.New("service name was not provided") 151 | } 152 | 153 | if w.Service.Path == nil { 154 | empty := "" 155 | w.Service.Path = &empty 156 | } 157 | 158 | if w.Service.Namespace == "" { 159 | w.Service.Namespace = "default" 160 | } 161 | 162 | return createURL(scheme, w.Service.Name, w.Service.Namespace, *w.Service.Path, w.Service.Port), nil 163 | } 164 | 165 | // moved to a separate method to cover it with unit tests and check that URL corresponds to a proper pattern 166 | func createURL(scheme, name, namespace, path string, port *int32) *url.URL { 167 | var hostPort int32 = 8000 168 | if port != nil { 169 | hostPort = *port 170 | } 171 | 172 | return &url.URL{ 173 | Scheme: scheme, 174 | Host: fmt.Sprintf("%s.%s.svc:%d", name, namespace, hostPort), 175 | Path: path, 176 | } 177 | } 178 | 179 | func setCABundle(caBundle []byte) error { 180 | // We can have multiple fleetautoscalers with different CABundles defined, 181 | // so we switch client.Transport before each POST request 182 | rootCAs := x509.NewCertPool() 183 | if ok := rootCAs.AppendCertsFromPEM(caBundle); !ok { 184 | return errors.New("no certs were appended from caBundle") 185 | } 186 | client.Transport = &http.Transport{ 187 | TLSClientConfig: &tls.Config{ 188 | RootCAs: rootCAs, 189 | }, 190 | } 191 | return nil 192 | } 193 | -------------------------------------------------------------------------------- /pkg/util/patch.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "encoding/json" 19 | 20 | "k8s.io/apimachinery/pkg/util/strategicpatch" 21 | ) 22 | 23 | // CreateMergePatch return kube generated from original and new interfaces 24 | func CreateMergePatch(original, new interface{}) ([]byte, error) { 25 | pvByte, err := json.Marshal(original) 26 | if err != nil { 27 | return nil, err 28 | } 29 | cloneByte, err := json.Marshal(new) 30 | if err != nil { 31 | return nil, err 32 | } 33 | patch, err := strategicpatch.CreateTwoWayMergePatch(pvByte, cloneByte, original) 34 | if err != nil { 35 | return nil, err 36 | } 37 | return patch, nil 38 | } 39 | -------------------------------------------------------------------------------- /pkg/util/signal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "os" 19 | "os/signal" 20 | "syscall" 21 | ) 22 | 23 | var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM} 24 | var onlyOneSignalHandler = make(chan struct{}) 25 | var shutdownHandler chan os.Signal 26 | 27 | // SetupSignalHandler registered for SIGTERM and SIGINT. A stop channel is returned 28 | // which is closed on one of these signals. If a second signal is caught, the program 29 | // is terminated with exit code 1. 30 | func SetupSignalHandler() <-chan struct{} { 31 | close(onlyOneSignalHandler) // panics when called twice 32 | 33 | shutdownHandler = make(chan os.Signal, 2) 34 | 35 | stop := make(chan struct{}) 36 | signal.Notify(shutdownHandler, shutdownSignals...) 37 | go func() { 38 | <-shutdownHandler 39 | close(stop) 40 | <-shutdownHandler 41 | os.Exit(1) // second signal. Exit directly. 42 | }() 43 | 44 | return stop 45 | } 46 | -------------------------------------------------------------------------------- /pkg/validation/validation.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // Copyright 2021 The Kubernetes Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package validation 17 | 18 | import ( 19 | "fmt" 20 | 21 | "github.com/robfig/cron" 22 | "k8s.io/api/admissionregistration/v1beta1" 23 | apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" 24 | pathvalidation "k8s.io/apimachinery/pkg/api/validation/path" 25 | "k8s.io/apimachinery/pkg/util/sets" 26 | "k8s.io/apimachinery/pkg/util/validation/field" 27 | "k8s.io/apiserver/pkg/util/webhook" 28 | 29 | autoscaling "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling/v1alpha1" 30 | ) 31 | 32 | const ( 33 | // MaxPeriodSeconds is the largest allowed scaling policy period (in seconds) 34 | MaxPeriodSeconds int32 = 1800 35 | // MaxStabilizationWindowSeconds is the largest allowed stabilization window (in seconds) 36 | MaxStabilizationWindowSeconds int32 = 3600 37 | ) 38 | 39 | // ValidateHorizontalPodAutoscalerName can be used to check whether the given autoscaler name is valid. 40 | // Prefix indicates this name will be used as part of generation, in which case trailing dashes are allowed. 41 | var ValidateHorizontalPodAutoscalerName = apimachineryvalidation.NameIsDNSSubdomain 42 | 43 | func validateHorizontalPodAutoscalerSpec(autoscaler autoscaling.GeneralPodAutoscalerSpec, fldPath *field.Path, 44 | minReplicasLowerBound int32) field.ErrorList { 45 | allErrs := field.ErrorList{} 46 | 47 | if autoscaler.MinReplicas != nil && *autoscaler.MinReplicas < minReplicasLowerBound { 48 | allErrs = append(allErrs, field.Invalid(fldPath.Child("minReplicas"), *autoscaler.MinReplicas, 49 | fmt.Sprintf("must be greater than or equal to %d", minReplicasLowerBound))) 50 | } 51 | if autoscaler.MaxReplicas < 1 { 52 | allErrs = append(allErrs, field.Invalid(fldPath.Child("maxReplicas"), autoscaler.MaxReplicas, "must be greater than 0")) 53 | } 54 | if autoscaler.MinReplicas != nil && autoscaler.MaxReplicas < *autoscaler.MinReplicas { 55 | allErrs = append(allErrs, field.Invalid(fldPath.Child("maxReplicas"), autoscaler.MaxReplicas, "must be greater than or equal to `minReplicas`")) 56 | } 57 | if refErrs := ValidateCrossVersionObjectReference(autoscaler.ScaleTargetRef, fldPath.Child("scaleTargetRef")); len(refErrs) > 0 { 58 | allErrs = append(allErrs, refErrs...) 59 | } 60 | if autoscaler.AutoScalingDrivenMode.MetricMode != nil { 61 | if refErrs := validateMetrics(autoscaler.AutoScalingDrivenMode.MetricMode.Metrics, fldPath.Child("metrics"), autoscaler.MinReplicas); len(refErrs) > 0 { 62 | allErrs = append(allErrs, refErrs...) 63 | } 64 | } 65 | if autoscaler.AutoScalingDrivenMode.WebhookMode != nil { 66 | if refErrs := validateWebhook(autoscaler.AutoScalingDrivenMode.WebhookMode.WebhookClientConfig, fldPath.Child("webhook")); len(refErrs) > 0 { 67 | allErrs = append(allErrs, refErrs...) 68 | } 69 | } 70 | if autoscaler.AutoScalingDrivenMode.TimeMode != nil { 71 | if refErrs := validateTime(autoscaler.AutoScalingDrivenMode.TimeMode.TimeRanges, fldPath.Child("time")); len(refErrs) > 0 { 72 | allErrs = append(allErrs, refErrs...) 73 | } 74 | } 75 | if autoscaler.AutoScalingDrivenMode.EventMode != nil { 76 | if refErrs := validateEvent(autoscaler.AutoScalingDrivenMode.EventMode.Triggers, fldPath.Child("event")); len(refErrs) > 0 { 77 | allErrs = append(allErrs, refErrs...) 78 | } 79 | } 80 | if refErrs := validateBehavior(autoscaler.Behavior, fldPath.Child("behavior")); len(refErrs) > 0 { 81 | allErrs = append(allErrs, refErrs...) 82 | } 83 | return allErrs 84 | } 85 | 86 | // ValidateCrossVersionObjectReference validates a CrossVersionObjectReference and returns an 87 | // ErrorList with any errors. 88 | func ValidateCrossVersionObjectReference(ref autoscaling.CrossVersionObjectReference, fldPath *field.Path) field.ErrorList { 89 | allErrs := field.ErrorList{} 90 | if len(ref.Kind) == 0 { 91 | allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "")) 92 | } else { 93 | for _, msg := range pathvalidation.IsValidPathSegmentName(ref.Kind) { 94 | allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), ref.Kind, msg)) 95 | } 96 | } 97 | 98 | if len(ref.Name) == 0 { 99 | allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) 100 | } else { 101 | for _, msg := range pathvalidation.IsValidPathSegmentName(ref.Name) { 102 | allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), ref.Name, msg)) 103 | } 104 | } 105 | 106 | return allErrs 107 | } 108 | 109 | // ValidateHorizontalPodAutoscaler validates a HorizontalPodAutoscaler and returns an 110 | // ErrorList with any errors. 111 | func ValidateHorizontalPodAutoscaler(autoscaler *autoscaling.GeneralPodAutoscaler) field.ErrorList { 112 | allErrs := apimachineryvalidation.ValidateObjectMeta(&autoscaler.ObjectMeta, true, ValidateHorizontalPodAutoscalerName, 113 | field.NewPath("metadata")) 114 | 115 | // MinReplicasLowerBound represents a minimum value for minReplicas 116 | // 0 when GPA scale-to-zero feature is enabled 117 | var minReplicasLowerBound int32 118 | 119 | allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(autoscaler.Spec, field.NewPath("spec"), minReplicasLowerBound)...) 120 | return allErrs 121 | } 122 | 123 | // ValidateHorizontalPodAutoscalerUpdate validates an update to a HorizontalPodAutoscaler and returns an 124 | // ErrorList with any errors. 125 | func ValidateHorizontalPodAutoscalerUpdate(newAutoscaler, oldAutoscaler *autoscaling.GeneralPodAutoscaler) field.ErrorList { 126 | allErrs := apimachineryvalidation.ValidateObjectMetaUpdate(&newAutoscaler.ObjectMeta, &oldAutoscaler.ObjectMeta, field.NewPath("metadata")) 127 | 128 | // minReplicasLowerBound represents a minimum value for minReplicas 129 | // 0 when GPA scale-to-zero feature is enabled or GPA object already has minReplicas=0 130 | var minReplicasLowerBound int32 131 | allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(newAutoscaler.Spec, field.NewPath("spec"), minReplicasLowerBound)...) 132 | return allErrs 133 | } 134 | 135 | // ValidateHorizontalPodAutoscalerStatusUpdate validates an update to status on a HorizontalPodAutoscaler and 136 | // returns an ErrorList with any errors. 137 | func ValidateHorizontalPodAutoscalerStatusUpdate(newAutoscaler, oldAutoscaler *autoscaling.GeneralPodAutoscaler) field.ErrorList { 138 | allErrs := apimachineryvalidation.ValidateObjectMetaUpdate(&newAutoscaler.ObjectMeta, &oldAutoscaler.ObjectMeta, field.NewPath("metadata")) 139 | status := newAutoscaler.Status 140 | allErrs = append(allErrs, apimachineryvalidation.ValidateNonnegativeField(int64(status.CurrentReplicas), field.NewPath("status", "currentReplicas"))...) 141 | allErrs = append(allErrs, apimachineryvalidation.ValidateNonnegativeField(int64(status.DesiredReplicas), field.NewPath("status", "desiredReplicas"))...) 142 | return allErrs 143 | } 144 | 145 | func validateMetrics(metrics []autoscaling.MetricSpec, fldPath *field.Path, minReplicas *int32) field.ErrorList { 146 | allErrs := field.ErrorList{} 147 | hasObjectMetrics := false 148 | hasExternalMetrics := false 149 | 150 | for i, metricSpec := range metrics { 151 | idxPath := fldPath.Index(i) 152 | if targetErrs := validateMetricSpec(metricSpec, idxPath); len(targetErrs) > 0 { 153 | allErrs = append(allErrs, targetErrs...) 154 | } 155 | if metricSpec.Type == autoscaling.ObjectMetricSourceType { 156 | hasObjectMetrics = true 157 | } 158 | if metricSpec.Type == autoscaling.ExternalMetricSourceType { 159 | hasExternalMetrics = true 160 | } 161 | } 162 | 163 | if minReplicas != nil && *minReplicas == 0 { 164 | if !hasObjectMetrics && !hasExternalMetrics { 165 | allErrs = append(allErrs, field.Forbidden(fldPath, "must specify at least one Object or External metric to support scaling to zero replicas")) 166 | } 167 | } 168 | 169 | return allErrs 170 | } 171 | 172 | func validateWebhook(wc *v1beta1.WebhookClientConfig, fldPath *field.Path) field.ErrorList { 173 | allErrs := field.ErrorList{} 174 | if wc == nil { 175 | allErrs = append(allErrs, field.Forbidden(fldPath, "webhook config should not be empty")) 176 | } 177 | switch { 178 | case wc.Service == nil && wc.URL == nil: 179 | allErrs = append(allErrs, field.Forbidden(fldPath, "must specify at least one service or url")) 180 | 181 | case wc.URL != nil: 182 | allErrs = append(allErrs, webhook.ValidateWebhookURL(fldPath.Child("webhook").Child("url"), *wc.URL, false)...) 183 | case wc.Service != nil: 184 | var port int32 = 0 185 | if wc.Service.Port != nil { 186 | port = *wc.Service.Port 187 | } 188 | allErrs = append(allErrs, webhook.ValidateWebhookService(fldPath.Child("webhook").Child("service"), wc.Service.Name, wc.Service.Namespace, 189 | wc.Service.Path, port)...) 190 | } 191 | return allErrs 192 | } 193 | 194 | func validateTime(timeRanges []autoscaling.TimeRange, fldPath *field.Path) field.ErrorList { 195 | allErrs := field.ErrorList{} 196 | if len(timeRanges) == 0 { 197 | allErrs = append(allErrs, field.Forbidden(fldPath.Child("timeRanges"), "at least one timeRanges should set")) 198 | } 199 | for _, timeRange := range timeRanges { 200 | if timeRange.DesiredReplicas == 0 { 201 | allErrs = append(allErrs, field.Forbidden(fldPath.Child("desiredReplicas"), "should not 0")) 202 | } 203 | if len(timeRange.Schedule) == 0 { 204 | allErrs = append(allErrs, field.Forbidden(fldPath.Child("schedule"), "should not empty")) 205 | } else { 206 | _, err := cron.Parse(timeRange.Schedule) 207 | if err != nil { 208 | allErrs = append(allErrs, field.Forbidden(fldPath.Child("schedule"), err.Error())) 209 | } 210 | } 211 | } 212 | return allErrs 213 | } 214 | 215 | func validateEvent(triggers []autoscaling.ScaleTriggers, fldPath *field.Path) field.ErrorList { 216 | allErrs := field.ErrorList{} 217 | if len(triggers) == 0 { 218 | allErrs = append(allErrs, field.Forbidden(fldPath.Child("triggers"), "at least one trigger should set")) 219 | } 220 | for _, trigger := range triggers { 221 | if len(trigger.Type) == 0 { 222 | allErrs = append(allErrs, field.Forbidden(fldPath.Child("type"), "trigger type must set")) 223 | 224 | } 225 | if len(trigger.Metadata) == 0 { 226 | allErrs = append(allErrs, field.Forbidden(fldPath.Child("medadata"), "trigger medadata must set")) 227 | } 228 | } 229 | return allErrs 230 | } 231 | 232 | func validateBehavior(behavior *autoscaling.GeneralPodAutoscalerBehavior, fldPath *field.Path) field.ErrorList { 233 | allErrs := field.ErrorList{} 234 | if behavior != nil { 235 | if scaleUpErrs := validateScalingRules(behavior.ScaleUp, fldPath.Child("scaleUp")); len(scaleUpErrs) > 0 { 236 | allErrs = append(allErrs, scaleUpErrs...) 237 | } 238 | if scaleDownErrs := validateScalingRules(behavior.ScaleDown, fldPath.Child("scaleDown")); len(scaleDownErrs) > 0 { 239 | allErrs = append(allErrs, scaleDownErrs...) 240 | } 241 | } 242 | return allErrs 243 | } 244 | 245 | var validSelectPolicyTypes = sets.NewString(string(autoscaling.MaxPolicySelect), string(autoscaling.MinPolicySelect), string(autoscaling.DisabledPolicySelect)) 246 | var validSelectPolicyTypesList = validSelectPolicyTypes.List() 247 | 248 | func validateScalingRules(rules *autoscaling.GPAScalingRules, fldPath *field.Path) field.ErrorList { 249 | allErrs := field.ErrorList{} 250 | if rules != nil { 251 | if rules.StabilizationWindowSeconds != nil && *rules.StabilizationWindowSeconds < 0 { 252 | allErrs = append(allErrs, field.Invalid(fldPath.Child("stabilizationWindowSeconds"), rules.StabilizationWindowSeconds, "must be greater than or equal to zero")) 253 | } 254 | if rules.StabilizationWindowSeconds != nil && *rules.StabilizationWindowSeconds > MaxStabilizationWindowSeconds { 255 | allErrs = append(allErrs, field.Invalid(fldPath.Child("stabilizationWindowSeconds"), rules.StabilizationWindowSeconds, 256 | fmt.Sprintf("must be less than or equal to %v", MaxStabilizationWindowSeconds))) 257 | } 258 | if rules.SelectPolicy != nil && !validSelectPolicyTypes.Has(string(*rules.SelectPolicy)) { 259 | allErrs = append(allErrs, field.NotSupported(fldPath.Child("selectPolicy"), rules.SelectPolicy, validSelectPolicyTypesList)) 260 | } 261 | policiesPath := fldPath.Child("policies") 262 | if len(rules.Policies) == 0 { 263 | allErrs = append(allErrs, field.Required(policiesPath, "must specify at least one Policy")) 264 | } 265 | for i, policy := range rules.Policies { 266 | idxPath := policiesPath.Index(i) 267 | if policyErrs := validateScalingPolicy(policy, idxPath); len(policyErrs) > 0 { 268 | allErrs = append(allErrs, policyErrs...) 269 | } 270 | } 271 | } 272 | return allErrs 273 | } 274 | 275 | var validPolicyTypes = sets.NewString(string(autoscaling.PodsScalingPolicy), string(autoscaling.PercentScalingPolicy)) 276 | var validPolicyTypesList = validPolicyTypes.List() 277 | 278 | func validateScalingPolicy(policy autoscaling.GPAScalingPolicy, fldPath *field.Path) field.ErrorList { 279 | allErrs := field.ErrorList{} 280 | if policy.Type != autoscaling.PodsScalingPolicy && policy.Type != autoscaling.PercentScalingPolicy { 281 | allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), policy.Type, validPolicyTypesList)) 282 | } 283 | if policy.Value <= 0 { 284 | allErrs = append(allErrs, field.Invalid(fldPath.Child("value"), policy.Value, "must be greater than zero")) 285 | } 286 | if policy.PeriodSeconds <= 0 { 287 | allErrs = append(allErrs, field.Invalid(fldPath.Child("periodSeconds"), policy.PeriodSeconds, "must be greater than zero")) 288 | } 289 | if policy.PeriodSeconds > MaxPeriodSeconds { 290 | allErrs = append(allErrs, field.Invalid(fldPath.Child("periodSeconds"), policy.PeriodSeconds, 291 | fmt.Sprintf("must be less than or equal to %v", MaxPeriodSeconds))) 292 | } 293 | return allErrs 294 | } 295 | 296 | var validMetricSourceTypes = sets.NewString( 297 | string(autoscaling.ObjectMetricSourceType), 298 | string(autoscaling.PodsMetricSourceType), 299 | string(autoscaling.ResourceMetricSourceType), 300 | string(autoscaling.ContainerResourceMetricSourceType), 301 | string(autoscaling.ExternalMetricSourceType)) 302 | var validMetricSourceTypesList = validMetricSourceTypes.List() 303 | 304 | func validateMetricSpec(spec autoscaling.MetricSpec, fldPath *field.Path) field.ErrorList { 305 | allErrs := field.ErrorList{} 306 | 307 | if len(string(spec.Type)) == 0 { 308 | allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must specify a metric source type")) 309 | } 310 | 311 | if !validMetricSourceTypes.Has(string(spec.Type)) { 312 | allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), spec.Type, validMetricSourceTypesList)) 313 | } 314 | 315 | typesPresent := sets.NewString() 316 | if spec.Object != nil { 317 | typesPresent.Insert("object") 318 | if typesPresent.Len() == 1 { 319 | allErrs = append(allErrs, validateObjectSource(spec.Object, fldPath.Child("object"))...) 320 | } 321 | } 322 | 323 | if spec.External != nil { 324 | typesPresent.Insert("external") 325 | if typesPresent.Len() == 1 { 326 | allErrs = append(allErrs, validateExternalSource(spec.External, fldPath.Child("external"))...) 327 | } 328 | } 329 | 330 | if spec.Pods != nil { 331 | typesPresent.Insert("pods") 332 | if typesPresent.Len() == 1 { 333 | allErrs = append(allErrs, validatePodsSource(spec.Pods, fldPath.Child("pods"))...) 334 | } 335 | } 336 | 337 | if spec.Resource != nil { 338 | typesPresent.Insert("resource") 339 | if typesPresent.Len() == 1 { 340 | allErrs = append(allErrs, validateResourceSource(spec.Resource, fldPath.Child("resource"))...) 341 | } 342 | } 343 | 344 | var expectedField string 345 | switch spec.Type { 346 | 347 | case autoscaling.ObjectMetricSourceType: 348 | if spec.Object == nil { 349 | allErrs = append(allErrs, field.Required(fldPath.Child("object"), "must populate information for the given metric source")) 350 | } 351 | expectedField = "object" 352 | case autoscaling.PodsMetricSourceType: 353 | if spec.Pods == nil { 354 | allErrs = append(allErrs, field.Required(fldPath.Child("pods"), "must populate information for the given metric source")) 355 | } 356 | expectedField = "pods" 357 | case autoscaling.ResourceMetricSourceType: 358 | if spec.Resource == nil { 359 | allErrs = append(allErrs, field.Required(fldPath.Child("resource"), "must populate information for the given metric source")) 360 | } 361 | expectedField = "resource" 362 | case autoscaling.ContainerResourceMetricSourceType: 363 | if spec.ContainerResource == nil { 364 | allErrs = append(allErrs, field.Required(fldPath.Child("containerResource"), "must populate information for the given metric source")) 365 | } 366 | expectedField = "containerResource" 367 | case autoscaling.ExternalMetricSourceType: 368 | if spec.External == nil { 369 | allErrs = append(allErrs, field.Required(fldPath.Child("external"), "must populate information for the given metric source")) 370 | } 371 | expectedField = "external" 372 | default: 373 | allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), spec.Type, validMetricSourceTypesList)) 374 | } 375 | 376 | if typesPresent.Len() != 1 { 377 | typesPresent.Delete(expectedField) 378 | for typ := range typesPresent { 379 | allErrs = append(allErrs, field.Forbidden(fldPath.Child(typ), "must populate the given metric source only")) 380 | } 381 | } 382 | 383 | return allErrs 384 | } 385 | 386 | func validateObjectSource(src *autoscaling.ObjectMetricSource, fldPath *field.Path) field.ErrorList { 387 | allErrs := field.ErrorList{} 388 | 389 | allErrs = append(allErrs, ValidateCrossVersionObjectReference(src.DescribedObject, fldPath.Child("describedObject"))...) 390 | allErrs = append(allErrs, validateMetricIdentifier(src.Metric, fldPath.Child("metric"))...) 391 | allErrs = append(allErrs, validateMetricTarget(src.Target, fldPath.Child("target"))...) 392 | 393 | if src.Target.Value == nil && src.Target.AverageValue == nil { 394 | allErrs = append(allErrs, field.Required(fldPath.Child("target").Child("averageValue"), "must set either a target value or averageValue")) 395 | } 396 | 397 | return allErrs 398 | } 399 | 400 | func validateExternalSource(src *autoscaling.ExternalMetricSource, fldPath *field.Path) field.ErrorList { 401 | allErrs := field.ErrorList{} 402 | 403 | allErrs = append(allErrs, validateMetricIdentifier(src.Metric, fldPath.Child("metric"))...) 404 | allErrs = append(allErrs, validateMetricTarget(src.Target, fldPath.Child("target"))...) 405 | 406 | if src.Target.Value == nil && src.Target.AverageValue == nil { 407 | allErrs = append(allErrs, field.Required(fldPath.Child("target").Child("averageValue"), "must set either a target value for metric or a per-pod target")) 408 | } 409 | 410 | if src.Target.Value != nil && src.Target.AverageValue != nil { 411 | allErrs = append(allErrs, field.Forbidden(fldPath.Child("target").Child("value"), "may not set both a target value for metric and a per-pod target")) 412 | } 413 | 414 | return allErrs 415 | } 416 | 417 | func validatePodsSource(src *autoscaling.PodsMetricSource, fldPath *field.Path) field.ErrorList { 418 | allErrs := field.ErrorList{} 419 | 420 | allErrs = append(allErrs, validateMetricIdentifier(src.Metric, fldPath.Child("metric"))...) 421 | allErrs = append(allErrs, validateMetricTarget(src.Target, fldPath.Child("target"))...) 422 | 423 | if src.Target.AverageValue == nil { 424 | allErrs = append(allErrs, field.Required(fldPath.Child("target").Child("averageValue"), "must specify a positive target averageValue")) 425 | } 426 | 427 | return allErrs 428 | } 429 | 430 | func validateResourceSource(src *autoscaling.ResourceMetricSource, fldPath *field.Path) field.ErrorList { 431 | allErrs := field.ErrorList{} 432 | 433 | if len(src.Name) == 0 { 434 | allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must specify a resource name")) 435 | } 436 | 437 | allErrs = append(allErrs, validateMetricTarget(src.Target, fldPath.Child("target"))...) 438 | 439 | if src.Target.AverageUtilization == nil && src.Target.AverageValue == nil { 440 | allErrs = append(allErrs, field.Required(fldPath.Child("target").Child("averageUtilization"), "must set either a target raw value or a target utilization")) 441 | } 442 | 443 | if src.Target.AverageUtilization != nil && src.Target.AverageValue != nil { 444 | allErrs = append(allErrs, field.Forbidden(fldPath.Child("target").Child("averageValue"), "may not set both a target raw value and a target utilization")) 445 | } 446 | 447 | return allErrs 448 | } 449 | 450 | func validateMetricTarget(mt autoscaling.MetricTarget, fldPath *field.Path) field.ErrorList { 451 | allErrs := field.ErrorList{} 452 | 453 | if len(mt.Type) == 0 { 454 | allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must specify a metric target type")) 455 | } 456 | 457 | if mt.Type != autoscaling.UtilizationMetricType && 458 | mt.Type != autoscaling.ValueMetricType && 459 | mt.Type != autoscaling.AverageValueMetricType { 460 | allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), mt.Type, "must be either Utilization, Value, or AverageValue")) 461 | } 462 | 463 | if mt.AverageUtilization == nil && mt.AverageValue == nil && mt.Value == nil { 464 | allErrs = append(allErrs, field.Invalid(fldPath.Child(" utilization, value and averageValue"), mt.Type, "at least one not nil")) 465 | } 466 | 467 | if mt.Value != nil && mt.Value.Sign() != 1 { 468 | allErrs = append(allErrs, field.Invalid(fldPath.Child("value"), mt.Value, "must be positive")) 469 | } 470 | 471 | if mt.AverageValue != nil && mt.AverageValue.Sign() != 1 { 472 | allErrs = append(allErrs, field.Invalid(fldPath.Child("averageValue"), mt.AverageValue, "must be positive")) 473 | } 474 | 475 | if mt.AverageUtilization != nil && *mt.AverageUtilization < 1 { 476 | allErrs = append(allErrs, field.Invalid(fldPath.Child("averageUtilization"), mt.AverageUtilization, "must be greater than 0")) 477 | } 478 | 479 | return allErrs 480 | } 481 | 482 | func validateMetricIdentifier(id autoscaling.MetricIdentifier, fldPath *field.Path) field.ErrorList { 483 | allErrs := field.ErrorList{} 484 | 485 | if len(id.Name) == 0 { 486 | allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must specify a metric name")) 487 | } else { 488 | for _, msg := range pathvalidation.IsValidPathSegmentName(id.Name) { 489 | allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), id.Name, msg)) 490 | } 491 | } 492 | return allErrs 493 | } 494 | -------------------------------------------------------------------------------- /pkg/validator/hook.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package webhook 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "io/ioutil" 21 | "net/http" 22 | 23 | "k8s.io/api/admission/v1beta1" 24 | admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" 25 | corev1 "k8s.io/api/core/v1" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | "k8s.io/apimachinery/pkg/runtime" 28 | "k8s.io/apimachinery/pkg/runtime/serializer" 29 | "k8s.io/apimachinery/pkg/util/validation/field" 30 | "k8s.io/klog" 31 | 32 | "github.com/ocgi/general-pod-autoscaler/pkg/apis/autoscaling/v1alpha1" 33 | "github.com/ocgi/general-pod-autoscaler/pkg/validation" 34 | ) 35 | 36 | var ( 37 | runtimeScheme = runtime.NewScheme() 38 | codecs = serializer.NewCodecFactory(runtimeScheme) 39 | deserializer = codecs.UniversalDeserializer() 40 | ) 41 | 42 | type webhookServer struct { 43 | *http.Server 44 | } 45 | 46 | func init() { 47 | _ = corev1.AddToScheme(runtimeScheme) 48 | _ = admissionregistrationv1beta1.AddToScheme(runtimeScheme) 49 | runtimeScheme.AddKnownTypes(v1alpha1.SchemeGroupVersion) 50 | } 51 | 52 | func NewWebhookServer() *webhookServer { 53 | return &webhookServer{} 54 | } 55 | 56 | // validate deployments and services 57 | func (whsvr *webhookServer) mutate(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse { 58 | req := ar.Request 59 | 60 | klog.Infof("AdmissionReview for Kind=%v, Namespace=%v Name=%v UID=%v Operation=%v UserInfo=%v", 61 | req.Kind, req.Namespace, req.Name, req.UID, req.Operation, req.UserInfo) 62 | var err error 63 | var patch []byte 64 | var causes []metav1.StatusCause 65 | switch req.Kind.Kind { 66 | case "GeneralPodAutoscaler": 67 | patch, causes, err = forGPA(req) 68 | 69 | default: 70 | return &v1beta1.AdmissionResponse{ 71 | Allowed: false, 72 | } 73 | } 74 | klog.V(6).Infof("Final patch %+v", string(patch)) 75 | 76 | result := metav1.Status{ 77 | Details: &metav1.StatusDetails{ 78 | Name: ar.Request.Name, 79 | Group: ar.Request.Kind.Group, 80 | Kind: ar.Request.Kind.Kind, 81 | UID: ar.Request.UID, 82 | }, 83 | } 84 | if err != nil { 85 | klog.Error(err) 86 | result.Code = 400 87 | result.Message = err.Error() 88 | result.Details.Causes = causes 89 | return &v1beta1.AdmissionResponse{ 90 | Allowed: false, 91 | Result: &result, 92 | } 93 | } 94 | jsonPatch := v1beta1.PatchTypeJSONPatch 95 | return &v1beta1.AdmissionResponse{ 96 | Allowed: true, 97 | Result: &result, 98 | Patch: patch, 99 | PatchType: &jsonPatch, 100 | } 101 | } 102 | 103 | // Serve method for webhook server 104 | func (whsvr *webhookServer) Serve(w http.ResponseWriter, r *http.Request) { 105 | var body []byte 106 | if r.Body != nil { 107 | if data, err := ioutil.ReadAll(r.Body); err == nil { 108 | body = data 109 | } 110 | } 111 | klog.Info(r.URL.RawPath) 112 | klog.V(6).Infof("Receive request: %+v", *r) 113 | if len(body) == 0 { 114 | klog.Error("empty body") 115 | http.Error(w, "empty body", http.StatusBadRequest) 116 | return 117 | } 118 | 119 | // verify the content type is accurate 120 | contentType := r.Header.Get("Content-Type") 121 | if contentType != "application/json" { 122 | klog.Errorf("Content-Type=%s, expect application/json", contentType) 123 | http.Error(w, "invalid Content-Type, expect `application/json`", http.StatusUnsupportedMediaType) 124 | return 125 | } 126 | 127 | var admissionResponse *v1beta1.AdmissionResponse 128 | ar := v1beta1.AdmissionReview{} 129 | if _, _, err := deserializer.Decode(body, nil, &ar); err != nil { 130 | klog.Errorf("Can't decode body: %v", err) 131 | admissionResponse = &v1beta1.AdmissionResponse{ 132 | Result: &metav1.Status{ 133 | Message: err.Error(), 134 | }, 135 | } 136 | } else { 137 | fmt.Println(r.URL.Path) 138 | if r.URL.Path == "/mutate" { 139 | admissionResponse = whsvr.mutate(&ar) 140 | } 141 | } 142 | 143 | admissionReview := v1beta1.AdmissionReview{} 144 | if admissionResponse != nil { 145 | admissionReview.Response = admissionResponse 146 | if ar.Request != nil { 147 | admissionReview.Response.UID = ar.Request.UID 148 | } 149 | } 150 | 151 | resp, err := json.Marshal(admissionReview) 152 | if err != nil { 153 | klog.Errorf("Can't encode response: %v", err) 154 | http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError) 155 | } 156 | if _, err := w.Write(resp); err != nil { 157 | klog.Errorf("Can't write response: %v", err) 158 | http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError) 159 | } 160 | } 161 | 162 | func forGPA(req *v1beta1.AdmissionRequest) ([]byte, []metav1.StatusCause, error) { 163 | var errs field.ErrorList 164 | causes := make([]metav1.StatusCause, 0) 165 | defer func() { 166 | if len(errs) == 0 { 167 | return 168 | } 169 | for i := range errs { 170 | err := errs[i] 171 | causes = append(causes, metav1.StatusCause{ 172 | Type: metav1.CauseType(err.Type), 173 | Message: err.ErrorBody(), 174 | Field: err.Field, 175 | }) 176 | } 177 | }() 178 | var gpa, oldGPA v1alpha1.GeneralPodAutoscaler 179 | if err := json.Unmarshal(req.Object.Raw, &gpa); err != nil { 180 | klog.Errorf("Could not unmarshal raw object: %v", err) 181 | return nil, nil, err 182 | } 183 | if req.Operation == v1beta1.Create { 184 | // validate 185 | errs = validation.ValidateHorizontalPodAutoscaler(&gpa) 186 | if len(errs) > 0 { 187 | return nil, causes, errs.ToAggregate() 188 | } 189 | } 190 | if req.Operation == v1beta1.Update { 191 | if err := json.Unmarshal(req.OldObject.Raw, &oldGPA); err != nil { 192 | klog.Errorf("Could not unmarshal old raw object: %v", err) 193 | return nil, nil, err 194 | } 195 | // validate 196 | errs = validation.ValidateHorizontalPodAutoscalerUpdate(&gpa, &oldGPA) 197 | if len(errs) > 0 { 198 | return nil, causes, errs.ToAggregate() 199 | } 200 | } 201 | return nil, nil, nil 202 | } 203 | -------------------------------------------------------------------------------- /pkg/version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The OCGI Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package version 16 | 17 | import ( 18 | "fmt" 19 | "runtime" 20 | 21 | "k8s.io/klog" 22 | ) 23 | 24 | var ( 25 | // Version describes the components version 26 | Version = "default" 27 | // Commit describes the components commit 28 | Commit = "default" 29 | // BuildTime describes the components build time 30 | BuildTime = "unknow" 31 | ) 32 | 33 | // Print prints the components version 34 | func Print() { 35 | klog.Info(fmt.Sprintf("Version: %s", Version)) 36 | klog.Info(fmt.Sprintf("Commit: %s", Commit)) 37 | klog.Info(fmt.Sprintf("BuildTime: %s", BuildTime)) 38 | klog.Info(fmt.Sprintf("Go Version: %s", runtime.Version())) 39 | klog.Info(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH)) 40 | } 41 | --------------------------------------------------------------------------------