diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1e206de4..d0b8fe09 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,7 +12,7 @@ on: - release-* env: - GO_VERSION: '1.19' + GO_VERSION: '1.23' jobs: @@ -57,21 +57,20 @@ jobs: name: Golang Lint runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache: false - - name: golangci-lint - uses: golangci/golangci-lint-action@v3 - with: - # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: v1.53 - args: --timeout=30m + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + check-latest: true + cache-dependency-path: "**/*.sum" + - name: golangci-lint + uses: golangci/golangci-lint-action@v7 + with: + # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version + version: v2.0.2 + args: --timeout=30m # Lints Pull Request commits with commitlint. # diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index a2e953a5..247a004b 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -10,7 +10,7 @@ on: - release-* env: - GO_VERSION: '1.19' + GO_VERSION: '1.23' KIND_VERSION: 'v0.14.0' KIND_IMAGE: 'kindest/node:v1.22.2' KIND_CLUSTER_NAME: 'e2e-test' diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 5ec27373..eb0c612f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -8,7 +8,7 @@ on: permissions: contents: write env: - GO_VERSION: '1.19' + GO_VERSION: '1.23' jobs: Test: @@ -46,20 +46,20 @@ jobs: name: Golang Lint runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache: false - - name: golangci-lint - uses: golangci/golangci-lint-action@v3 - with: - version: v1.53 - args: --timeout=30m + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + check-latest: true + cache-dependency-path: "**/*.sum" + - name: golangci-lint + uses: golangci/golangci-lint-action@v7 + with: + # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version + version: v2.0.2 + args: --timeout=30m # Release the artifacts, release note and images. @@ -99,7 +99,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.19 + go-version: 1.23 - name: Release with GoReleaser uses: goreleaser/goreleaser-action@v2 diff --git a/.golangci.yaml b/.golangci.yaml index bbe1443a..6c199066 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,10 +1,78 @@ -run: - skip-files: - - ".*_test.go" - skip-dirs: - - vendor/ - +version: "2" linters: - disable: - - errcheck - - unused + default: standard + enable: + - bodyclose + - copyloopvar + - ginkgolinter + - testifylint + - misspell + - nakedret + - unconvert + - unparam + - whitespace + - gocritic + - errorlint + settings: + govet: + enable: + - shadow + misspell: + locale: US + staticcheck: + checks: + - "-QF1008" + gocritic: + enable-all: true + disabled-checks: + - emptyStringTest + - ifElseChain + - singleCaseSwitch + - hugeParam + - unnamedResult + - whyNoLint + - tooManyResultsChecker + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - errcheck + - unparam + path: _test.go + - linters: + - govet + text: declaration of "err" shadows declaration at line + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofumpt # drop in replacement for gofmt + - gci # format imports + settings: + gci: + custom-order: true + sections: + - standard + - blank + - dot + - default + - localmodule + gofumpt: + extra-rules: true + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ + +run: + allow-parallel-runners: true + timeout: 10m \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ad58d4a9..1296bd8b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM golang:1.19-alpine3.17 as builder +FROM golang:1.23-alpine3.22 as builder ARG TARGETOS ARG TARGETARCH @@ -23,7 +23,7 @@ COPY apis/ apis/ # by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} go build -a -o manager main.go -FROM alpine:3.17 +FROM alpine:3.22 WORKDIR / COPY --from=builder /workspace/manager . diff --git a/charts/templates/crd/apps.kusionstack.io_podtransitionrules.yaml b/charts/templates/crd/apps.kusionstack.io_podtransitionrules.yaml index 09d6b9c3..5e3f04bf 100644 --- a/charts/templates/crd/apps.kusionstack.io_podtransitionrules.yaml +++ b/charts/templates/crd/apps.kusionstack.io_podtransitionrules.yaml @@ -50,20 +50,56 @@ spec: description: AvailablePolicy is the rule to check if the max unavailable number is reached by current resource updated. properties: + maxUnavailablePolicy: + description: MaxUnavailablePolicy is an option to apply + max unavailable by adaptive policy. + properties: + expFunc: + description: |- + ExpFunc is an exponentiation expression to calculate the expected value. + func: f(x) = coeff * x ^ pow. + properties: + coeff: + description: Coeff stands for coefficient in this + exponentiation. + type: string + pow: + description: Pow stands for power in this exponentiation. + type: string + type: object + type: object maxUnavailableValue: anyOf: - type: integer - type: string description: |- - MaxUnavailableValue is the expected max unavailable replicas which is allowed to be a integer or a percentage of the whole + MaxUnavailableValue is the expected max unavailable replicas which is allowed to be an integer or a percentage of the whole number of the target resources. x-kubernetes-int-or-string: true + minAvailablePolicy: + description: MaxUnavailablePolicy is an option to apply + min available by adaptive policy. + properties: + expFunc: + description: |- + ExpFunc is an exponentiation expression to calculate the expected value. + func: f(x) = coeff * x ^ pow. + properties: + coeff: + description: Coeff stands for coefficient in this + exponentiation. + type: string + pow: + description: Pow stands for power in this exponentiation. + type: string + type: object + type: object minAvailableValue: anyOf: - type: integer - type: string description: |- - MinAvailableValue is the expected min available replicas which is allowed to be a integer or a percentage of the whole + MinAvailableValue is the expected min available replicas which is allowed to be an integer or a percentage of the whole number of the target resources. x-kubernetes-int-or-string: true type: object diff --git a/config/crd/bases/apps.kusionstack.io_podtransitionrules.yaml b/config/crd/bases/apps.kusionstack.io_podtransitionrules.yaml index 09d6b9c3..5e3f04bf 100644 --- a/config/crd/bases/apps.kusionstack.io_podtransitionrules.yaml +++ b/config/crd/bases/apps.kusionstack.io_podtransitionrules.yaml @@ -50,20 +50,56 @@ spec: description: AvailablePolicy is the rule to check if the max unavailable number is reached by current resource updated. properties: + maxUnavailablePolicy: + description: MaxUnavailablePolicy is an option to apply + max unavailable by adaptive policy. + properties: + expFunc: + description: |- + ExpFunc is an exponentiation expression to calculate the expected value. + func: f(x) = coeff * x ^ pow. + properties: + coeff: + description: Coeff stands for coefficient in this + exponentiation. + type: string + pow: + description: Pow stands for power in this exponentiation. + type: string + type: object + type: object maxUnavailableValue: anyOf: - type: integer - type: string description: |- - MaxUnavailableValue is the expected max unavailable replicas which is allowed to be a integer or a percentage of the whole + MaxUnavailableValue is the expected max unavailable replicas which is allowed to be an integer or a percentage of the whole number of the target resources. x-kubernetes-int-or-string: true + minAvailablePolicy: + description: MaxUnavailablePolicy is an option to apply + min available by adaptive policy. + properties: + expFunc: + description: |- + ExpFunc is an exponentiation expression to calculate the expected value. + func: f(x) = coeff * x ^ pow. + properties: + coeff: + description: Coeff stands for coefficient in this + exponentiation. + type: string + pow: + description: Pow stands for power in this exponentiation. + type: string + type: object + type: object minAvailableValue: anyOf: - type: integer - type: string description: |- - MinAvailableValue is the expected min available replicas which is allowed to be a integer or a percentage of the whole + MinAvailableValue is the expected min available replicas which is allowed to be an integer or a percentage of the whole number of the target resources. x-kubernetes-int-or-string: true type: object diff --git a/go.mod b/go.mod index 8217f1cf..d0d50db2 100644 --- a/go.mod +++ b/go.mod @@ -1,28 +1,30 @@ module kusionstack.io/kuperator -go 1.19 +go 1.23 + +toolchain go1.23.8 require ( github.com/evanphx/json-patch v5.7.0+incompatible - github.com/go-logr/logr v1.4.1 + github.com/go-logr/logr v1.4.2 github.com/google/uuid v1.4.0 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.30.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.17.0 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.4 - golang.org/x/net v0.25.0 - k8s.io/api v0.28.4 - k8s.io/apimachinery v0.28.4 + github.com/stretchr/testify v1.10.0 + golang.org/x/net v0.28.0 + k8s.io/api v0.33.2 + k8s.io/apimachinery v0.33.2 k8s.io/client-go v0.28.4 k8s.io/component-base v0.28.4 k8s.io/component-helpers v0.22.6 k8s.io/klog v1.0.0 - k8s.io/klog/v2 v2.100.1 + k8s.io/klog/v2 v2.130.1 k8s.io/kubernetes v0.0.0-00010101000000-000000000000 - k8s.io/utils v0.0.0-20240102154912-e7106e64919e - kusionstack.io/kube-api v0.6.6 + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 + kusionstack.io/kube-api v0.7.4-0.20250728111735-11fa6b63ac0e kusionstack.io/kube-utils v0.1.19-0.20250206032419-01aec11b2bfa kusionstack.io/resourceconsist v0.0.1 sigs.k8s.io/controller-runtime v0.17.3 @@ -81,11 +83,11 @@ require ( github.com/tjfoc/gmsm v1.3.2 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.23.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/oauth2 v0.15.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/term v0.20.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/term v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect golang.org/x/time v0.5.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/appengine v1.6.8 // indirect @@ -99,7 +101,7 @@ require ( k8s.io/apiserver v0.22.6 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect k8s.io/kubectl v0.29.0 - sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index c880e1df..482e0d2f 100644 --- a/go.sum +++ b/go.sum @@ -413,6 +413,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -543,7 +544,8 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rubiojr/go-vhd v0.0.0-20200706105327-02e210299021/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -585,15 +587,16 @@ github.com/storageos/go-api v2.2.0+incompatible/go.mod h1:ZrLn+e0ZuF3Y65PNF6dIwb github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM= @@ -650,6 +653,7 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -673,8 +677,8 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -766,8 +770,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -857,8 +861,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -866,8 +870,8 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -881,8 +885,8 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1060,6 +1064,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -1144,10 +1149,10 @@ k8s.io/sample-apiserver v0.22.6/go.mod h1:wiamO3alvd/dicaP8PvTPCdqq31QKDbRxm1uqO k8s.io/system-validators v1.5.0/go.mod h1:bPldcLgkIUK22ALflnsXk8pvkTEndYdNuaHH6gRrl0Q= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= -k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -kusionstack.io/kube-api v0.6.6 h1:gMLUQL/eectQxkosnlv1m/R2xieY2crETliWRcxBICg= -kusionstack.io/kube-api v0.6.6/go.mod h1:J0+EHiroG/88X904Y9TV9iMRcoEuD5tXMTLMBDSwM+Y= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +kusionstack.io/kube-api v0.7.4-0.20250728111735-11fa6b63ac0e h1:4+G3cHiUfDMh45xWGmZA+36UIZ8eBTSov4wxr3/gekE= +kusionstack.io/kube-api v0.7.4-0.20250728111735-11fa6b63ac0e/go.mod h1:e1jtrQH2LK5fD2nTyfIXG6nYrYbU8VXShRxTRwVPaLk= kusionstack.io/kube-utils v0.1.19-0.20250206032419-01aec11b2bfa h1:2yZnTzVtgevpNixyE/oF29KIZ5LKiy9y1H8lxL+3BxU= kusionstack.io/kube-utils v0.1.19-0.20250206032419-01aec11b2bfa/go.mod h1:dm0cZBYBOiz+7GsHYP6AH7PDoSqoplTEes7RlxEJMHo= kusionstack.io/resourceconsist v0.0.1 h1:+k/jriq5Ld7fQUYfWSMGynz/FesHtl3Rk2fmQPjBe0g= @@ -1168,10 +1173,12 @@ sigs.k8s.io/kustomize/api v0.8.11/go.mod h1:a77Ls36JdfCWojpUqR6m60pdGY1AYFix4AH8 sigs.k8s.io/kustomize/cmd/config v0.9.13/go.mod h1:7547FLF8W/lTaDf0BDqFTbZxM9zqwEJqCKN9sSR0xSs= sigs.k8s.io/kustomize/kustomize/v4 v4.2.0/go.mod h1:MOkR6fmhwG7hEDRXBYELTi5GSFcLwfqwzTRHW3kv5go= sigs.k8s.io/kustomize/kyaml v0.11.0/go.mod h1:GNMwjim4Ypgp/MueD3zXHLRJEjz7RvtPae0AwlvEMFM= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016 h1:kXv6kKdoEtedwuqMmkqhbkgvYKeycVbC8+iPCP9j5kQ= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= -sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/pkg/controllers/podtransitionrule/processor/rules/available.go b/pkg/controllers/podtransitionrule/processor/rules/available.go index 3816e15c..8530d65b 100644 --- a/pkg/controllers/podtransitionrule/processor/rules/available.go +++ b/pkg/controllers/podtransitionrule/processor/rules/available.go @@ -19,6 +19,8 @@ package rules import ( "context" "fmt" + "math" + "strconv" "time" appsv1 "k8s.io/api/apps/v1" @@ -30,17 +32,19 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1alpha1 "kusionstack.io/kube-api/apps/v1alpha1" + "kusionstack.io/kuperator/pkg/controllers/podtransitionrule/register" "kusionstack.io/kuperator/pkg/controllers/podtransitionrule/utils" ) type AvailableRuler struct { - Name string - - MinAvailableValue *intstr.IntOrString - MaxUnavailableValue *intstr.IntOrString - + Name string Client client.Client + + MinAvailableValue *intstr.IntOrString + MaxUnavailableValue *intstr.IntOrString + MinAvailablePolicy *appsv1alpha1.AdaptivePolicy + MaxUnavailablePolicy *appsv1alpha1.AdaptivePolicy } // Filter unavailable pods and try approve available pods as much as possible @@ -51,11 +55,12 @@ func (r *AvailableRuler) Filter(podTransitionRule *appsv1alpha1.PodTransitionRul for _, t := range targets { effectiveTargets.Insert(t.Name) } - maxUnavailableQuota := len(effectiveTargets) + totalCnt := len(effectiveTargets) + maxUnavailableQuota := totalCnt allowUnavailable := maxUnavailableQuota minAvailableQuota := 0 if r.MaxUnavailableValue != nil { - quota, err := intstr.GetScaledValueFromIntOrPercent(r.MaxUnavailableValue, len(effectiveTargets), true) + quota, err := intstr.GetScaledValueFromIntOrPercent(r.MaxUnavailableValue, totalCnt, true) if err != nil { return rejectAllWithErr(subjects, pass, rejects, "[%s] fail to get int value from raw max unavailable value(%s), error: %v", r.Name, r.MaxUnavailableValue.String(), err) } @@ -63,13 +68,34 @@ func (r *AvailableRuler) Filter(podTransitionRule *appsv1alpha1.PodTransitionRul allowUnavailable = quota } + if r.MaxUnavailablePolicy != nil { + quota, coeff, pow, err := getValueFromExponentiation(r.MaxUnavailablePolicy.ExpFunc, totalCnt, true) + if err != nil { + return rejectAllWithErr(subjects, pass, rejects, "[%s] fail to get max unavailable value with ExpFunc(coeff: %f, pow: %f, total: %d), error: %s", r.Name, coeff, pow, totalCnt, err) + } + if quota < maxUnavailableQuota { + maxUnavailableQuota = quota + allowUnavailable = quota + } + } + if r.MinAvailableValue != nil { - quota, err := intstr.GetScaledValueFromIntOrPercent(r.MinAvailableValue, len(effectiveTargets), false) + quota, err := intstr.GetScaledValueFromIntOrPercent(r.MinAvailableValue, totalCnt, false) if err != nil { return rejectAllWithErr(subjects, pass, rejects, "[%s] fail to get int value from raw min available value(%s), error: %v", r.Name, r.MaxUnavailableValue.String(), err) } minAvailableQuota = quota } + + if r.MinAvailablePolicy != nil { + quota, coeff, pow, err := getValueFromExponentiation(r.MinAvailablePolicy.ExpFunc, totalCnt, false) + if err != nil { + return rejectAllWithErr(subjects, pass, rejects, "[%s] fail to get min unavailable value with ExpFunc(coeff: %f, pow: %f, total: %d), error: %s", r.Name, coeff, pow, totalCnt, err) + } + if quota > minAvailableQuota { + minAvailableQuota = quota + } + } // TODO: UncreatedReplicas // allowUnavailable -= uncreatedReplicas allAvailableSize := 0 @@ -121,10 +147,10 @@ func (r *AvailableRuler) Filter(podTransitionRule *appsv1alpha1.PodTransitionRul } for podName := range keepMinAvailablePods { - rejects[podName] = fmt.Sprintf("blocked by min available policy: [min available]=%d/%d, [current keep available]=%d/%d", minAvailableQuota, len(effectiveTargets), allAvailableSize, len(effectiveTargets)) + rejects[podName] = fmt.Sprintf("blocked by min available policy: [min available]=%d/%d, [current keep available]=%d/%d", minAvailableQuota, totalCnt, allAvailableSize, totalCnt) } for podName := range rejectByMaxUnavailablePods { - rejects[podName] = fmt.Sprintf("[%s] blocked by max unavailable policy: [max unavailable]=%d/%d, [current unavailable]=%d/%d", r.Name, maxUnavailableQuota, len(effectiveTargets), len(effectiveTargets)-allAvailableSize, len(effectiveTargets)) + rejects[podName] = fmt.Sprintf("[%s] blocked by max unavailable policy: [max unavailable]=%d/%d, [current unavailable]=%d/%d", r.Name, maxUnavailableQuota, totalCnt, totalCnt-allAvailableSize, totalCnt) } if minTimeLeft != nil { @@ -168,6 +194,47 @@ func processUnavailableFunc(pod *corev1.Pod) (bool, *int64) { return isUnavailable, minInterval } +func getValueFromExponentiation(exp *appsv1alpha1.ExpFunc, total int, roundUp bool) (int, float64, float64, error) { + pow := 0.7 + coeff := 1.0 + var err error + + if exp.Pow != nil { + pow, err = strconv.ParseFloat(*exp.Pow, 64) + if err != nil { + return 0, 0, 0, err + } + } + + if exp.Coeff != nil { + coeff, err = strconv.ParseFloat(*exp.Coeff, 64) + if err != nil { + return 0, 0, 0, err + } + } + + if total == 0 { + return 0, coeff, pow, nil + } + + val := 0 + floatVal := coeff * math.Pow(float64(total), pow) + if roundUp { + val = int(math.Ceil(floatVal)) + } else { + val = int(math.Floor(floatVal)) + } + if val < 0 { + return 0, coeff, pow, nil + } + + if val > total { + return total, coeff, pow, nil + } + + return val, coeff, pow, nil +} + func min(a, b *int64) *int64 { if a == nil { return b diff --git a/pkg/controllers/podtransitionrule/processor/rules/available_test.go b/pkg/controllers/podtransitionrule/processor/rules/available_test.go new file mode 100644 index 00000000..d3c20213 --- /dev/null +++ b/pkg/controllers/podtransitionrule/processor/rules/available_test.go @@ -0,0 +1,111 @@ +/* +Copyright 2025 The KusionStack Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rules + +import ( + "testing" + + "k8s.io/utils/ptr" + appsv1alpha1 "kusionstack.io/kube-api/apps/v1alpha1" +) + +func TestExpFuncCal(t *testing.T) { + expFunc := &appsv1alpha1.ExpFunc{ + Coeff: ptr.To("1.0"), + Pow: ptr.To("0.7"), + } + + if val, _, _, _ := getValueFromExponentiation(expFunc, 1, false); val != 1 { + t.Fatalf("unexpected %d", val) + } + + if val, _, _, _ := getValueFromExponentiation(expFunc, 2, false); val != 1 { + t.Fatalf("unexpected %d", val) + } + + if val, _, _, _ := getValueFromExponentiation(expFunc, 10, false); val != 5 { + t.Fatalf("unexpected %d", val) + } + + if val, _, _, _ := getValueFromExponentiation(expFunc, 50, false); val != 15 { + t.Fatalf("unexpected %d", val) + } + + if val, _, _, _ := getValueFromExponentiation(expFunc, 500, false); val != 77 { + t.Fatalf("unexpected %d", val) + } + + if val, _, _, _ := getValueFromExponentiation(expFunc, 1000, false); val != 125 { + t.Fatalf("unexpected %d", val) + } +} + +func TestExpFuncCalException(t *testing.T) { + expFunc := &appsv1alpha1.ExpFunc{ + Coeff: ptr.To("2.0"), + Pow: ptr.To("0.7"), + } + + if val, _, _, _ := getValueFromExponentiation(expFunc, 10, false); val != 10 { + t.Fatalf("unexpected %d", val) + } + + expFunc = &appsv1alpha1.ExpFunc{ + Coeff: ptr.To("0.0"), + Pow: ptr.To("0.7"), + } + + if val, _, _, _ := getValueFromExponentiation(expFunc, 10, false); val != 0 { + t.Fatalf("unexpected %d", val) + } + + expFunc = &appsv1alpha1.ExpFunc{ + Coeff: ptr.To("0.0"), + Pow: ptr.To("0.0"), + } + + if val, _, _, _ := getValueFromExponentiation(expFunc, 10, false); val != 0 { + t.Fatalf("unexpected %d", val) + } + + expFunc = &appsv1alpha1.ExpFunc{ + Coeff: ptr.To("1.0"), + Pow: ptr.To("0.0"), + } + + if val, _, _, _ := getValueFromExponentiation(expFunc, 10, false); val != 1 { + t.Fatalf("unexpected %d", val) + } + + expFunc = &appsv1alpha1.ExpFunc{ + Coeff: ptr.To("-1.0"), + Pow: ptr.To("-0.7"), + } + + if val, _, _, _ := getValueFromExponentiation(expFunc, 1, false); val != 0 { + t.Fatalf("unexpected %d", val) + } + + expFunc = &appsv1alpha1.ExpFunc{ + Coeff: nil, + Pow: nil, + } + + if val, _, _, _ := getValueFromExponentiation(expFunc, 10, false); val != 5 { + t.Fatalf("unexpected %d", val) + } +} diff --git a/pkg/controllers/podtransitionrule/processor/rules/types.go b/pkg/controllers/podtransitionrule/processor/rules/types.go index 670273d8..c86c828f 100644 --- a/pkg/controllers/podtransitionrule/processor/rules/types.go +++ b/pkg/controllers/podtransitionrule/processor/rules/types.go @@ -44,10 +44,12 @@ func GetRuler(rule *appsv1alpha1.TransitionRule, client client.Client) Ruler { if rule.AvailablePolicy != nil { return &AvailableRuler{ - Client: client, - MinAvailableValue: rule.AvailablePolicy.MinAvailableValue, - MaxUnavailableValue: rule.AvailablePolicy.MaxUnavailableValue, - Name: rule.Name, + Client: client, + MinAvailableValue: rule.AvailablePolicy.MinAvailableValue, + MaxUnavailableValue: rule.AvailablePolicy.MaxUnavailableValue, + MinAvailablePolicy: rule.AvailablePolicy.MinAvailablePolicy, + MaxUnavailablePolicy: rule.AvailablePolicy.MaxUnavailablePolicy, + Name: rule.Name, } } if rule.LabelCheck != nil { diff --git a/releaser.Dockerfile b/releaser.Dockerfile index e310a290..ad47a4d4 100644 --- a/releaser.Dockerfile +++ b/releaser.Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.17 +FROM alpine:3.22 WORKDIR / COPY manager /manager