mirror of
https://github.com/kubernetes-sigs/descheduler.git
synced 2026-08-30 12:47:17 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a0830ae0b | ||
|
|
9914e71ece | ||
|
|
8bbd0bd661 | ||
|
|
b66b54f6c0 | ||
|
|
5aca81876e | ||
|
|
ef090e93de | ||
|
|
bdb6f3e42d | ||
|
|
4233637de9 | ||
|
|
6fe697934e | ||
|
|
fb908401dd | ||
|
|
ad19067697 | ||
|
|
73bcc99a5e | ||
|
|
82b96a392e | ||
|
|
5c3469c3ff | ||
|
|
9d09c0d65f | ||
|
|
0b2a396ac1 | ||
|
|
e08a32dfa8 | ||
|
|
b5a214909c | ||
|
|
ec736d7140 | ||
|
|
51ecda87fb | ||
|
|
39a2b0c685 | ||
|
|
3691abd9ac | ||
|
|
4471d02801 | ||
|
|
0614367253 | ||
|
|
7d2b28bf2b | ||
|
|
cf2e95cb60 | ||
|
|
ea957b271b | ||
|
|
bb14e73150 | ||
|
|
341aba2723 | ||
|
|
607b578980 | ||
|
|
436b0ac076 | ||
|
|
86183b15a2 | ||
|
|
81fb28c18c | ||
|
|
3588dacd57 | ||
|
|
9cba672e8f | ||
|
|
5ac9278456 | ||
|
|
8005cdf783 | ||
|
|
745146e411 | ||
|
|
2119baee3d | ||
|
|
b767b9c0f7 | ||
|
|
03f7dc5889 | ||
|
|
dfc3166787 | ||
|
|
0883de763b | ||
|
|
3f8f9bb74e | ||
|
|
d6720f509c | ||
|
|
66918dc553 | ||
|
|
6b5f6b450c | ||
|
|
3ea8051e1e | ||
|
|
5fd4c1ea00 | ||
|
|
57105044ad | ||
|
|
b5a7c076f3 | ||
|
|
a7ac072c7d | ||
|
|
c00d88f9ed | ||
|
|
290d7f3dfb | ||
|
|
fab0f2ab70 | ||
|
|
ddadd33ab2 | ||
|
|
7be855f7a5 | ||
|
|
67a20369b8 | ||
|
|
e56144c7a2 | ||
|
|
fa8ae489ae | ||
|
|
bc0f0354c6 | ||
|
|
0bc278a816 | ||
|
|
212b706950 | ||
|
|
8f9d5c607d | ||
|
|
1ca2edbb59 | ||
|
|
0fafc09fff | ||
|
|
a4391ea73b | ||
|
|
a845ed3b36 | ||
|
|
ac815c26f6 | ||
|
|
e76287fbbf | ||
|
|
751ba2e76e | ||
|
|
d82437286b | ||
|
|
905e762603 | ||
|
|
cbdab93459 | ||
|
|
af6e2adf42 | ||
|
|
c5d8ec102b | ||
|
|
9bfdbe92e9 | ||
|
|
9e9595357a | ||
|
|
2b356b25bf |
@@ -0,0 +1 @@
|
||||
vendor/**
|
||||
@@ -20,25 +20,25 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v4.2.0
|
||||
uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 # v4.2.0
|
||||
with:
|
||||
version: v3.15.1
|
||||
|
||||
- uses: actions/setup-python@v5.1.1
|
||||
- uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1
|
||||
with:
|
||||
python-version: 3.12
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- name: Set up chart-testing
|
||||
uses: helm/chart-testing-action@v2.8.0
|
||||
uses: helm/chart-testing-action@6ec842c01de15ebb84c8627d2744a0c2f2755c9f # v2.8.0
|
||||
with:
|
||||
version: v3.11.0
|
||||
|
||||
|
||||
@@ -7,23 +7,35 @@ jobs:
|
||||
deploy:
|
||||
strategy:
|
||||
matrix:
|
||||
k8s-version: ["v1.35.1"]
|
||||
descheduler-version: ["v0.35.0"]
|
||||
k8s-version: ["v1.36.1"]
|
||||
build-kind-node-image: [true]
|
||||
kind-node-image: ["localhost/kindest/node:v1.36.1"]
|
||||
descheduler-version: ["v0.36.0"]
|
||||
descheduler-api: ["v1alpha2"]
|
||||
manifest: ["deployment"]
|
||||
kind-version: ["v0.31.0"] # keep in sync with test/run-e2e-tests.sh
|
||||
kind-version: ["v0.31.0"] # keep in sync with test/lib/e2e-versions.env
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Create kind cluster
|
||||
uses: helm/kind-action@v1.12.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # git ls-remote https://github.com/actions/checkout.git refs/tags/v6.0.2
|
||||
- name: Install kind
|
||||
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # git ls-remote https://github.com/helm/kind-action.git refs/tags/v1.14.0
|
||||
with:
|
||||
node_image: kindest/node:${{ matrix.k8s-version }}
|
||||
version: ${{ matrix.kind-version }}
|
||||
kubectl_version: ${{ matrix.k8s-version }}
|
||||
install_only: true
|
||||
- name: Build kind node image
|
||||
if: ${{ matrix.build-kind-node-image == true }}
|
||||
run: |
|
||||
kind build node-image ${{ matrix.k8s-version }} --image ${{ matrix.kind-node-image }}
|
||||
- name: Create kind cluster
|
||||
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # git ls-remote https://github.com/helm/kind-action.git refs/tags/v1.14.0
|
||||
with:
|
||||
node_image: ${{ matrix.kind-node-image }}
|
||||
kubectl_version: ${{ matrix.k8s-version }}
|
||||
config: test/kind-config.yaml
|
||||
version: ${{ matrix.kind-version }}
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # git ls-remote https://github.com/actions/setup-go.git refs/tags/v6.4.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -23,12 +23,12 @@ jobs:
|
||||
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||
|
||||
- name: Install Helm
|
||||
uses: azure/setup-helm@v4.2.0
|
||||
uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 # v4.2.0
|
||||
with:
|
||||
version: v3.15.1
|
||||
|
||||
- name: Run chart-releaser
|
||||
uses: helm/chart-releaser-action@v1.6.0
|
||||
uses: helm/chart-releaser-action@a917fd15b20e8b64b94d9158ad54cd6345335584 # v1.6.0
|
||||
env:
|
||||
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
CR_RELEASE_NAME_TEMPLATE: "descheduler-helm-chart-{{ .Version }}"
|
||||
|
||||
@@ -17,12 +17,9 @@ jobs:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -32,7 +29,7 @@ jobs:
|
||||
IMAGE_TAG=${HELM_IMAGE_TAG:-security-test}
|
||||
VERSION=security-test make image
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@master
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
|
||||
with:
|
||||
image-ref: 'descheduler:security-test'
|
||||
format: 'sarif'
|
||||
@@ -41,7 +38,6 @@ jobs:
|
||||
output: 'trivy-results.sarif'
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
uses: github/codeql-action/upload-sarif@7c1e4cf0b20d7c1872b26569c00ba908797a59bf # v4
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
exit-code: '0'
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
# 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.
|
||||
FROM golang:1.25.1
|
||||
FROM golang:1.26.0
|
||||
|
||||
WORKDIR /go/src/sigs.k8s.io/descheduler
|
||||
COPY . .
|
||||
|
||||
@@ -29,7 +29,7 @@ LDFLAGS=-ldflags "-X ${LDFLAG_LOCATION}.version=${VERSION} -X ${LDFLAG_LOCATION}
|
||||
GOLANGCI_VERSION := v2.8.0
|
||||
HAS_GOLANGCI := $(shell ls _output/bin/golangci-lint 2> /dev/null)
|
||||
|
||||
GOFUMPT_VERSION := v0.7.0
|
||||
GOFUMPT_VERSION := v0.10.0
|
||||
HAS_GOFUMPT := $(shell command -v gofumpt 2> /dev/null)
|
||||
|
||||
GO_VERSION := $(shell (command -v jq > /dev/null && (go mod edit -json | jq -r .Go)) || (sed -En 's/^go (.*)$$/\1/p' go.mod))
|
||||
@@ -146,7 +146,7 @@ verify-gen:
|
||||
|
||||
lint:
|
||||
ifndef HAS_GOLANGCI
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b ./_output/bin ${GOLANGCI_VERSION}
|
||||
GOBIN=$(CURRENT_DIR)/_output/bin go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${GOLANGCI_VERSION}
|
||||
endif
|
||||
./_output/bin/golangci-lint run -v
|
||||
./_output/bin/golangci-lint fmt -v
|
||||
|
||||
@@ -33,11 +33,12 @@ but relies on the default scheduler for that.
|
||||
## ⚠️ Documentation Versions by Release
|
||||
|
||||
If you are using a published release of Descheduler (such as
|
||||
`registry.k8s.io/descheduler/descheduler:v0.35.0`), follow the documentation in
|
||||
`registry.k8s.io/descheduler/descheduler:v0.36.0`), follow the documentation in
|
||||
that version's release branch, as listed below:
|
||||
|
||||
|Descheduler Version|Docs link|
|
||||
|---|---|
|
||||
|v0.36.x|[`release-1.36`](https://github.com/kubernetes-sigs/descheduler/blob/release-1.36/README.md)|
|
||||
|v0.35.x|[`release-1.35`](https://github.com/kubernetes-sigs/descheduler/blob/release-1.35/README.md)|
|
||||
|v0.34.x|[`release-1.34`](https://github.com/kubernetes-sigs/descheduler/blob/release-1.34/README.md)|
|
||||
|v0.33.x|[`release-1.33`](https://github.com/kubernetes-sigs/descheduler/blob/release-1.33/README.md)|
|
||||
@@ -297,7 +298,7 @@ Balance Plugins: These plugins process all pods, or groups of pods, and determin
|
||||
| [RemovePodsViolatingNodeTaints](#removepodsviolatingnodetaints) |Deschedule|Evicts pods violating node taints|
|
||||
| [RemovePodsViolatingTopologySpreadConstraint](#removepodsviolatingtopologyspreadconstraint) |Balance|Evicts pods violating TopologySpreadConstraints|
|
||||
| [RemovePodsHavingTooManyRestarts](#removepodshavingtoomanyrestarts) |Deschedule|Evicts pods having too many restarts|
|
||||
| [PodLifeTime](#podlifetime) |Deschedule|Evicts pods that have exceeded a specified age limit|
|
||||
| [PodLifeTime](#podlifetime) |Deschedule|Evicts pods based on age, status transitions, conditions, states, exit codes, and owner kinds|
|
||||
| [RemoveFailedPods](#removefailedpods) |Deschedule|Evicts pods with certain failed reasons and exit codes|
|
||||
|
||||
|
||||
@@ -785,30 +786,52 @@ profiles:
|
||||
|
||||
### PodLifeTime
|
||||
|
||||
This strategy evicts pods that are older than `maxPodLifeTimeSeconds`.
|
||||
This strategy evicts pods based on their age, status transitions, conditions, states, exit codes, and owner kinds. It supports both simple age-based eviction and fine-grained cleanup of pods matching specific transition criteria.
|
||||
|
||||
You can also specify `states` parameter to **only** evict pods matching the following conditions:
|
||||
> The primary purpose for using states like `Succeeded` and `Failed` is releasing resources so that new pods can be rescheduled.
|
||||
> I.e., the main motivation is not for cleaning pods, rather to release resources.
|
||||
- [Pod Phase](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase) status of: `Running`, `Pending`, `Succeeded`, `Failed`, `Unknown`
|
||||
- [Pod Reason](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-conditions) reasons of: `NodeAffinity`, `NodeLost`, `Shutdown`, `UnexpectedAdmissionError`
|
||||
- [Container State Waiting](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-state-waiting) condition of: `PodInitializing`, `ContainerCreating`, `ImagePullBackOff`, `CrashLoopBackOff`, `CreateContainerConfigError`, `ErrImagePull`, `ImagePullBackOff`, `CreateContainerError`, `InvalidImageName`
|
||||
All non-empty filter categories are ANDed (a pod must satisfy every specified filter). Within each category, items are ORed (matching any one entry satisfies that filter). For `conditions`, a pod is eligible for eviction if **any** of the listed condition filters match — each filter is evaluated independently against the pod's `status.conditions[]` entries. Pods are processed from oldest to newest based on their creation time.
|
||||
|
||||
If a value for `states` or `podStatusPhases` is not specified,
|
||||
Pods in any state (even `Running`) are considered for eviction.
|
||||
See the [plugin README](pkg/framework/plugins/podlifetime/README.md) for detailed documentation and advanced use cases.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type | Notes |
|
||||
|--------------------------------|---------------------------------------------------|--------------------------|
|
||||
| `maxPodLifeTimeSeconds` | int | |
|
||||
| `states` | list(string) | Only supported in v0.25+ |
|
||||
| `includingInitContainers` | bool | Only supported in v0.31+ |
|
||||
| `includingEphemeralContainers` | bool | Only supported in v0.31+ |
|
||||
| `namespaces` | (see [namespace filtering](#namespace-filtering)) | |
|
||||
| `labelSelector` | (see [label filtering](#label-filtering)) | |
|
||||
| Name | Type | Notes |
|
||||
|------|------|-------|
|
||||
| `conditions` | list(object) | Each with optional `type`, `status`, `reason`, `minTimeSinceLastTransitionSeconds` fields |
|
||||
| `exitCodes` | list(int32) | Container terminated exit codes |
|
||||
| `includingEphemeralContainers` | bool | Extend state filtering to ephemeral containers |
|
||||
| `includingInitContainers` | bool | Extend state/exitCode filtering to init containers |
|
||||
| `labelSelector` | (see [label filtering](#label-filtering)) | |
|
||||
| `maxPodLifeTimeSeconds` | uint | Pods older than this many seconds are evicted |
|
||||
| `namespaces` | (see [namespace filtering](#namespace-filtering)) | |
|
||||
| `ownerKinds` | object | `include` or `exclude` list of owner reference kinds |
|
||||
| `states` | list(string) | Pod phases, pod status reasons, container waiting/terminated reasons |
|
||||
|
||||
**Example:**
|
||||
**Example (transition-based eviction):**
|
||||
|
||||
```yaml
|
||||
apiVersion: "descheduler/v1alpha2"
|
||||
kind: "DeschedulerPolicy"
|
||||
profiles:
|
||||
- name: ProfileName
|
||||
pluginConfig:
|
||||
- name: "PodLifeTime"
|
||||
args:
|
||||
states:
|
||||
- "Succeeded"
|
||||
conditions:
|
||||
- reason: "PodCompleted"
|
||||
status: "True"
|
||||
minTimeSinceLastTransitionSeconds: 14400
|
||||
ownerKinds:
|
||||
exclude:
|
||||
- "Job"
|
||||
plugins:
|
||||
deschedule:
|
||||
enabled:
|
||||
- "PodLifeTime"
|
||||
```
|
||||
|
||||
**Example (age-based eviction):**
|
||||
|
||||
```yaml
|
||||
apiVersion: "descheduler/v1alpha2"
|
||||
@@ -829,6 +852,7 @@ profiles:
|
||||
```
|
||||
|
||||
### RemoveFailedPods
|
||||
|
||||
This strategy evicts pods that are in failed status phase.
|
||||
You can provide optional parameters to filter by failed pods' and containters' `reasons`. and `exitCodes`. `exitCodes` apply to failed pods' containers with `terminated` state only. `reasons` and `exitCodes` can be expanded to include those of InitContainers as well by setting the optional parameter `includingInitContainers` to `true`.
|
||||
You can specify an optional parameter `minPodLifetimeSeconds` to evict pods that are older than specified seconds.
|
||||
@@ -1136,6 +1160,7 @@ packages that it is compiled with.
|
||||
|
||||
| Descheduler | Supported Kubernetes Version |
|
||||
|-------------|------------------------------|
|
||||
| v0.36 | v1.36 |
|
||||
| v0.35 | v1.35 |
|
||||
| v0.34 | v1.34 |
|
||||
| v0.33 | v1.33 |
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
apiVersion: v1
|
||||
name: descheduler
|
||||
version: 0.35.0
|
||||
appVersion: 0.35.0
|
||||
version: 0.36.0
|
||||
appVersion: 0.36.0
|
||||
description: Descheduler for Kubernetes is used to rebalance clusters by evicting pods that can potentially be scheduled on better nodes. In the current implementation, descheduler does not schedule replacement of evicted pods but relies on the default scheduler for that.
|
||||
keywords:
|
||||
- kubernetes
|
||||
- descheduler
|
||||
- kube-scheduler
|
||||
home: https://github.com/kubernetes-sigs/descheduler
|
||||
icon: https://kubernetes.io/images/favicon.png
|
||||
icon: https://raw.githubusercontent.com/kubernetes-sigs/descheduler/master/assets/logo/descheduler-stacked-color.png
|
||||
sources:
|
||||
- https://github.com/kubernetes-sigs/descheduler
|
||||
maintainers:
|
||||
|
||||
@@ -62,9 +62,17 @@ The following table lists the configurable parameters of the _descheduler_ chart
|
||||
| `ttlSecondsAfterFinished` | If set, configure `ttlSecondsAfterFinished` for the _descheduler_ job | `nil` |
|
||||
| `deschedulingInterval` | If using kind:Deployment, sets time between consecutive descheduler executions. | `5m` |
|
||||
| `replicas` | The replica count for Deployment | `1` |
|
||||
| `revisionHistoryLimit | The revision history limit for Deployment | `10` |
|
||||
| `leaderElection` | The options for high availability when running replicated components | _see values.yaml_ |
|
||||
| `podDisruptionBudget.enabled` | If `true` and `kind` is `Deployment`, create a PodDisruptionBudget for the descheduler | `false` |
|
||||
| `podDisruptionBudget.minAvailable` | Minimum descheduler pods that must stay available (mutually exclusive with `maxUnavailable`) | `1` |
|
||||
| `podDisruptionBudget.maxUnavailable`| Maximum descheduler pods that can be unavailable (mutually exclusive with `minAvailable`) | `""` |
|
||||
| `podDisruptionBudget.annotations` | Annotations to add to the PodDisruptionBudget | `{}` |
|
||||
| `cmdOptions` | The options to pass to the _descheduler_ command | _see values.yaml_ |
|
||||
| `priorityClassName` | The name of the priority class to add to pods | `system-cluster-critical` |
|
||||
| `schedulerName` | The name of the scheduler used to schedule the descheduler cronjob/deployment pods | `""` |
|
||||
| `runtimeClassName` | The RuntimeClass applied to the descheduler cronjob/deployment pods | `""` |
|
||||
| `hostUsers` | If set (`true`/`false`), sets `hostUsers` on the pod spec to control user-namespace sharing | `nil` |
|
||||
| `rbac.create` | If `true`, create & use RBAC resources | `true` |
|
||||
| `resources` | Descheduler container CPU and memory requests/limits | _see values.yaml_ |
|
||||
| `serviceAccount.create` | If `true`, create a service account for the cron job | `true` |
|
||||
@@ -79,6 +87,7 @@ The following table lists the configurable parameters of the _descheduler_ chart
|
||||
| `nodeSelector` | Node selectors to run the descheduler cronjob/deployment on specific nodes | `nil` |
|
||||
| `service.enabled` | If `true`, create a service for deployment | `false` |
|
||||
| `serviceMonitor.enabled` | If `true`, create a ServiceMonitor for deployment | `false` |
|
||||
| `serviceMonitor.apiVersion` | API version used by the ServiceMonitor resource | `monitoring.coreos.com/v1` |
|
||||
| `serviceMonitor.namespace` | The namespace where Prometheus expects to find service monitors | `nil` |
|
||||
| `serviceMonitor.additionalLabels` | Add custom labels to the ServiceMonitor resource | `{}` |
|
||||
| `serviceMonitor.interval` | The scrape interval. If not set, the Prometheus default scrape interval is used | `nil` |
|
||||
|
||||
@@ -17,7 +17,7 @@ rules:
|
||||
verbs: ["get", "watch", "list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "watch", "list", "delete"]
|
||||
verbs: ["get", "watch", "list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods/eviction"]
|
||||
verbs: ["create"]
|
||||
@@ -36,6 +36,9 @@ rules:
|
||||
resourceNames: ["{{ .Values.leaderElection.resourceName | default "descheduler" }}"]
|
||||
verbs: ["get", "patch", "delete"]
|
||||
{{- end }}
|
||||
- apiGroups: [""]
|
||||
resources: ["persistentvolumeclaims"]
|
||||
verbs: ["get", "watch", "list"]
|
||||
{{- if and .Values.deschedulerPolicy }}
|
||||
{{- range .Values.deschedulerPolicy.metricsProviders }}
|
||||
{{- if and (hasKey . "source") (eq .source "KubernetesMetrics") }}
|
||||
|
||||
@@ -87,6 +87,15 @@ spec:
|
||||
{{- if .Values.priorityClassName }}
|
||||
priorityClassName: {{ .Values.priorityClassName }}
|
||||
{{- end }}
|
||||
{{- if .Values.schedulerName }}
|
||||
schedulerName: {{ .Values.schedulerName }}
|
||||
{{- end }}
|
||||
{{- if .Values.runtimeClassName }}
|
||||
runtimeClassName: {{ .Values.runtimeClassName }}
|
||||
{{- end }}
|
||||
{{- if kindIs "bool" .Values.hostUsers }}
|
||||
hostUsers: {{ .Values.hostUsers }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ template "descheduler.serviceAccountName" . }}
|
||||
{{- if kindIs "bool" .Values.automountServiceAccountToken }}
|
||||
automountServiceAccountToken: {{ .Values.automountServiceAccountToken }}
|
||||
@@ -96,6 +105,10 @@ spec:
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- with .Values.initContainers }}
|
||||
initContainers:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default (printf "v%s" .Chart.AppVersion) }}"
|
||||
|
||||
@@ -18,6 +18,7 @@ spec:
|
||||
{{- else }}
|
||||
replicas: 1
|
||||
{{- end }}
|
||||
revisionHistoryLimit: {{ .Values.revisionHistoryLimit }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "descheduler.selectorLabels" . | nindent 6 }}
|
||||
@@ -41,6 +42,15 @@ spec:
|
||||
{{- if .Values.priorityClassName }}
|
||||
priorityClassName: {{ .Values.priorityClassName }}
|
||||
{{- end }}
|
||||
{{- if .Values.schedulerName }}
|
||||
schedulerName: {{ .Values.schedulerName }}
|
||||
{{- end }}
|
||||
{{- if .Values.runtimeClassName }}
|
||||
runtimeClassName: {{ .Values.runtimeClassName }}
|
||||
{{- end }}
|
||||
{{- if kindIs "bool" .Values.hostUsers }}
|
||||
hostUsers: {{ .Values.hostUsers }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ template "descheduler.serviceAccountName" . }}
|
||||
{{- if kindIs "bool" .Values.automountServiceAccountToken }}
|
||||
automountServiceAccountToken: {{ .Values.automountServiceAccountToken }}
|
||||
@@ -49,6 +59,10 @@ spec:
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- with .Values.initContainers }}
|
||||
initContainers:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default (printf "v%s" .Chart.AppVersion) }}"
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{{- if and (eq .Values.kind "Deployment") (.Values.podDisruptionBudget).enabled }}
|
||||
{{- if .Capabilities.APIVersions.Has "policy/v1/PodDisruptionBudget" }}
|
||||
apiVersion: policy/v1
|
||||
{{- else }}
|
||||
apiVersion: policy/v1beta1
|
||||
{{- end }}
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: {{ template "descheduler.fullname" . }}
|
||||
namespace: {{ include "descheduler.namespace" . }}
|
||||
labels:
|
||||
{{- include "descheduler.labels" . | nindent 4 }}
|
||||
{{- with .Values.podDisruptionBudget.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.podDisruptionBudget.minAvailable }}
|
||||
minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
|
||||
{{- end }}
|
||||
{{- if .Values.podDisruptionBudget.maxUnavailable }}
|
||||
maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }}
|
||||
{{- end }}
|
||||
{{- with .Values.podDisruptionBudget.unhealthyPodEvictionPolicy }}
|
||||
unhealthyPodEvictionPolicy: {{ . }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "descheduler.selectorLabels" . | nindent 6 }}
|
||||
{{- end }}
|
||||
@@ -1,6 +1,6 @@
|
||||
{{- if eq .Values.kind "Deployment" }}
|
||||
{{- if eq .Values.serviceMonitor.enabled true }}
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
apiVersion: {{ .Values.serviceMonitor.apiVersion | default "monitoring.coreos.com/v1" }}
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: {{ template "descheduler.fullname" . }}-servicemonitor
|
||||
|
||||
@@ -15,3 +15,53 @@ tests:
|
||||
asserts:
|
||||
- isKind:
|
||||
of: CronJob
|
||||
|
||||
- it: sets schedulerName when provided
|
||||
set:
|
||||
schedulerName: custom-scheduler
|
||||
template: templates/cronjob.yaml
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.jobTemplate.spec.template.spec.schedulerName
|
||||
value: custom-scheduler
|
||||
|
||||
- it: sets runtimeClassName when provided
|
||||
set:
|
||||
runtimeClassName: gvisor
|
||||
template: templates/cronjob.yaml
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.jobTemplate.spec.template.spec.runtimeClassName
|
||||
value: gvisor
|
||||
|
||||
- it: omits schedulerName and runtimeClassName by default
|
||||
template: templates/cronjob.yaml
|
||||
asserts:
|
||||
- notExists:
|
||||
path: spec.jobTemplate.spec.template.spec.schedulerName
|
||||
- notExists:
|
||||
path: spec.jobTemplate.spec.template.spec.runtimeClassName
|
||||
|
||||
- it: sets hostUsers to false when configured
|
||||
set:
|
||||
hostUsers: false
|
||||
template: templates/cronjob.yaml
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.jobTemplate.spec.template.spec.hostUsers
|
||||
value: false
|
||||
|
||||
- it: sets hostUsers to true when configured
|
||||
set:
|
||||
hostUsers: true
|
||||
template: templates/cronjob.yaml
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.jobTemplate.spec.template.spec.hostUsers
|
||||
value: true
|
||||
|
||||
- it: omits hostUsers by default
|
||||
template: templates/cronjob.yaml
|
||||
asserts:
|
||||
- notExists:
|
||||
path: spec.jobTemplate.spec.template.spec.hostUsers
|
||||
|
||||
@@ -47,3 +47,53 @@ tests:
|
||||
- contains:
|
||||
path: spec.template.spec.containers[0].args
|
||||
content: --leader-elect-resource-namespace=typo
|
||||
|
||||
- it: sets schedulerName when provided
|
||||
set:
|
||||
schedulerName: custom-scheduler
|
||||
template: templates/deployment.yaml
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.template.spec.schedulerName
|
||||
value: custom-scheduler
|
||||
|
||||
- it: sets runtimeClassName when provided
|
||||
set:
|
||||
runtimeClassName: gvisor
|
||||
template: templates/deployment.yaml
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.template.spec.runtimeClassName
|
||||
value: gvisor
|
||||
|
||||
- it: omits schedulerName and runtimeClassName by default
|
||||
template: templates/deployment.yaml
|
||||
asserts:
|
||||
- notExists:
|
||||
path: spec.template.spec.schedulerName
|
||||
- notExists:
|
||||
path: spec.template.spec.runtimeClassName
|
||||
|
||||
- it: sets hostUsers to false when configured
|
||||
set:
|
||||
hostUsers: false
|
||||
template: templates/deployment.yaml
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.template.spec.hostUsers
|
||||
value: false
|
||||
|
||||
- it: sets hostUsers to true when configured
|
||||
set:
|
||||
hostUsers: true
|
||||
template: templates/deployment.yaml
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.template.spec.hostUsers
|
||||
value: true
|
||||
|
||||
- it: omits hostUsers by default
|
||||
template: templates/deployment.yaml
|
||||
asserts:
|
||||
- notExists:
|
||||
path: spec.template.spec.hostUsers
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
suite: Test Descheduler PodDisruptionBudget
|
||||
|
||||
templates:
|
||||
- "*.yaml"
|
||||
|
||||
release:
|
||||
name: descheduler
|
||||
|
||||
tests:
|
||||
- it: is not created for the default CronJob kind even when enabled
|
||||
template: templates/poddisruptionbudget.yaml
|
||||
set:
|
||||
podDisruptionBudget:
|
||||
enabled: true
|
||||
asserts:
|
||||
- hasDocuments:
|
||||
count: 0
|
||||
|
||||
- it: is not created for Deployment kind unless enabled
|
||||
template: templates/poddisruptionbudget.yaml
|
||||
set:
|
||||
kind: Deployment
|
||||
asserts:
|
||||
- hasDocuments:
|
||||
count: 0
|
||||
|
||||
- it: creates a PodDisruptionBudget for Deployment kind when enabled
|
||||
template: templates/poddisruptionbudget.yaml
|
||||
set:
|
||||
kind: Deployment
|
||||
podDisruptionBudget:
|
||||
enabled: true
|
||||
asserts:
|
||||
- isKind:
|
||||
of: PodDisruptionBudget
|
||||
- equal:
|
||||
path: spec.minAvailable
|
||||
value: 1
|
||||
- notExists:
|
||||
path: spec.maxUnavailable
|
||||
- equal:
|
||||
path: spec.selector.matchLabels["app.kubernetes.io/name"]
|
||||
value: descheduler
|
||||
|
||||
- it: supports maxUnavailable instead of minAvailable
|
||||
template: templates/poddisruptionbudget.yaml
|
||||
set:
|
||||
kind: Deployment
|
||||
podDisruptionBudget:
|
||||
enabled: true
|
||||
minAvailable: ""
|
||||
maxUnavailable: 1
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.maxUnavailable
|
||||
value: 1
|
||||
- notExists:
|
||||
path: spec.minAvailable
|
||||
|
||||
- it: supports unhealthyPodEvictionPolicy and annotations
|
||||
template: templates/poddisruptionbudget.yaml
|
||||
set:
|
||||
kind: Deployment
|
||||
podDisruptionBudget:
|
||||
enabled: true
|
||||
unhealthyPodEvictionPolicy: IfHealthyBudget
|
||||
annotations:
|
||||
example.com/team: platform
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.unhealthyPodEvictionPolicy
|
||||
value: IfHealthyBudget
|
||||
- equal:
|
||||
path: metadata.annotations["example.com/team"]
|
||||
value: platform
|
||||
@@ -0,0 +1,31 @@
|
||||
suite: Test ServiceMonitor
|
||||
|
||||
templates:
|
||||
- "*.yaml"
|
||||
|
||||
release:
|
||||
name: descheduler
|
||||
|
||||
set:
|
||||
kind: Deployment
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
|
||||
tests:
|
||||
- it: uses default ServiceMonitor apiVersion
|
||||
template: templates/servicemonitor.yaml
|
||||
asserts:
|
||||
- equal:
|
||||
path: apiVersion
|
||||
value: monitoring.coreos.com/v1
|
||||
|
||||
- it: supports overriding ServiceMonitor apiVersion
|
||||
set:
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
apiVersion: azmonitoring.coreos.com/v1
|
||||
template: templates/servicemonitor.yaml
|
||||
asserts:
|
||||
- equal:
|
||||
path: apiVersion
|
||||
value: azmonitoring.coreos.com/v1
|
||||
@@ -68,6 +68,8 @@ deschedulingInterval: 5m
|
||||
# only if that node is in the same zone as at least one already-running descheduler
|
||||
replicas: 1
|
||||
|
||||
revisionHistoryLimit: 10
|
||||
|
||||
# Specifies whether Leader Election resources should be created
|
||||
# Required when running as a Deployment
|
||||
# NOTE: Leader election can't be activated if DryRun enabled
|
||||
@@ -80,6 +82,20 @@ leaderElection: {}
|
||||
# resourceName: "descheduler"
|
||||
# resourceNamespace: "kube-system"
|
||||
|
||||
# Pod Disruption Budget for the descheduler Deployment. Optional.
|
||||
# Only rendered when kind is "Deployment". The descheduler runs a single replica
|
||||
# by default, so this is disabled by default. Enable it for highly available
|
||||
# setups (replicas > 1 with leaderElection enabled) to keep a descheduler pod
|
||||
# available during voluntary node disruptions.
|
||||
podDisruptionBudget:
|
||||
enabled: false
|
||||
# minAvailable and maxUnavailable are mutually exclusive; set only one.
|
||||
minAvailable: 1
|
||||
maxUnavailable: ""
|
||||
# See https://kubernetes.io/docs/tasks/run-application/configure-pdb/
|
||||
# unhealthyPodEvictionPolicy: IfHealthyBudget
|
||||
annotations: {}
|
||||
|
||||
command:
|
||||
- "/bin/descheduler"
|
||||
|
||||
@@ -150,6 +166,19 @@ deschedulerPolicy:
|
||||
|
||||
priorityClassName: system-cluster-critical
|
||||
|
||||
# schedulerName is the name of the scheduler used to schedule the descheduler pods.
|
||||
# Leave empty to use the cluster default scheduler.
|
||||
schedulerName: ""
|
||||
|
||||
# runtimeClassName is the RuntimeClass applied to the descheduler pods.
|
||||
# Leave empty to use the cluster default runtime.
|
||||
runtimeClassName: ""
|
||||
|
||||
# Set the Pod spec `hostUsers` field to control user-namespace sharing.
|
||||
# Default is not set (the cluster default applies). Set to `false` to opt into
|
||||
# user-namespace isolation, or `true` to share the host user namespace.
|
||||
# hostUsers: false
|
||||
|
||||
nodeSelector: {}
|
||||
# foo: bar
|
||||
|
||||
@@ -233,6 +262,13 @@ livenessProbe:
|
||||
periodSeconds: 20
|
||||
timeoutSeconds: 5
|
||||
|
||||
# Init containers to run before the main descheduler container
|
||||
initContainers: []
|
||||
# Example:
|
||||
# - name: init-myservice
|
||||
# image: busybox:1.28
|
||||
# command: ['sh', '-c', 'echo The app is running! && sleep 10']
|
||||
|
||||
service:
|
||||
enabled: false
|
||||
# @param service.ipFamilyPolicy [string], support SingleStack, PreferDualStack and RequireDualStack
|
||||
@@ -248,6 +284,7 @@ service:
|
||||
|
||||
serviceMonitor:
|
||||
enabled: false
|
||||
apiVersion: "monitoring.coreos.com/v1"
|
||||
# The namespace where Prometheus expects to find service monitors.
|
||||
# namespace: ""
|
||||
# Add custom labels to the ServiceMonitor resource
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ timeout: 3600s
|
||||
options:
|
||||
substitution_option: ALLOW_LOOSE
|
||||
steps:
|
||||
- name: 'gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20211118-2f2d816b90'
|
||||
- name: 'gcr.io/k8s-staging-test-infra/gcb-docker-gcloud@sha256:ff388e0dc16351e96f8464e2e185b74a7578a5ccb7a112cf3393468e59e6e2d2' # v20260205-38cfa9523f
|
||||
entrypoint: make
|
||||
env:
|
||||
- DOCKER_CLI_EXPERIMENTAL=enabled
|
||||
|
||||
@@ -28,13 +28,13 @@ import (
|
||||
|
||||
"sigs.k8s.io/descheduler/cmd/descheduler/app/options"
|
||||
"sigs.k8s.io/descheduler/pkg/descheduler"
|
||||
"sigs.k8s.io/descheduler/pkg/features"
|
||||
"sigs.k8s.io/descheduler/pkg/tracing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/apiserver/pkg/server/healthz"
|
||||
"k8s.io/apiserver/pkg/server/mux"
|
||||
"k8s.io/component-base/featuregate"
|
||||
"k8s.io/component-base/logs"
|
||||
logsapi "k8s.io/component-base/logs/api/v1"
|
||||
_ "k8s.io/component-base/logs/json/register"
|
||||
@@ -49,7 +49,6 @@ func NewDeschedulerCommand(out io.Writer) *cobra.Command {
|
||||
klog.ErrorS(err, "unable to initialize server")
|
||||
}
|
||||
|
||||
featureGate := featuregate.NewFeatureGate()
|
||||
logConfig := logsapi.NewLoggingConfiguration()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@@ -58,7 +57,10 @@ func NewDeschedulerCommand(out io.Writer) *cobra.Command {
|
||||
Long: "The descheduler evicts pods which may be bound to less desired nodes",
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
logs.InitLogs()
|
||||
if logsapi.ValidateAndApply(logConfig, featureGate); err != nil {
|
||||
if err := features.DefaultMutableFeatureGate.SetFromMap(s.FeatureGates); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := logsapi.ValidateAndApply(logConfig, features.DefaultMutableFeatureGate); err != nil {
|
||||
return err
|
||||
}
|
||||
descheduler.SetupPlugins()
|
||||
@@ -80,9 +82,9 @@ func NewDeschedulerCommand(out io.Writer) *cobra.Command {
|
||||
}
|
||||
cmd.SetOut(out)
|
||||
flags := cmd.Flags()
|
||||
runtime.Must(logsapi.AddFeatureGates(features.DefaultMutableFeatureGate))
|
||||
s.AddFlags(flags)
|
||||
|
||||
runtime.Must(logsapi.AddFeatureGates(featureGate))
|
||||
logsapi.AddFlags(logConfig, flags)
|
||||
|
||||
return cmd
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright 2026 The Kubernetes 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 app
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewDeschedulerCommand_LoggingAlphaOptions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "LoggingAlphaOptions=false rejects --log-json-split-stream",
|
||||
args: []string{
|
||||
"--logging-format=json",
|
||||
"--log-json-split-stream",
|
||||
"--feature-gates=LoggingAlphaOptions=false",
|
||||
},
|
||||
wantErr: "LoggingAlphaOptions is disabled",
|
||||
},
|
||||
{
|
||||
name: "LoggingAlphaOptions=true accepts --log-json-split-stream",
|
||||
args: []string{
|
||||
"--logging-format=json",
|
||||
"--log-json-split-stream",
|
||||
"--feature-gates=LoggingAlphaOptions=true",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmd := NewDeschedulerCommand(io.Discard)
|
||||
if err := cmd.ParseFlags(tc.args); err != nil {
|
||||
t.Fatalf("ParseFlags(%v): %v", tc.args, err)
|
||||
}
|
||||
err := cmd.PreRunE(cmd, cmd.Flags().Args())
|
||||
if tc.wantErr == "" {
|
||||
if err != nil {
|
||||
t.Fatalf("expected PreRunE to succeed, got error: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("expected PreRunE to fail with %q, got nil", tc.wantErr)
|
||||
}
|
||||
if !strings.Contains(err.Error(), tc.wantErr) {
|
||||
t.Fatalf("expected PreRunE error to contain %q, got :%v", tc.wantErr, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,10 @@ descheduler [flags]
|
||||
--feature-gates mapStringBool A set of key=value pairs that describe feature gates for alpha/experimental features. Options are:
|
||||
AllAlpha=true|false (ALPHA - default=false)
|
||||
AllBeta=true|false (BETA - default=false)
|
||||
ContextualLogging=true|false (BETA - default=true)
|
||||
EvictionsInBackground=true|false (ALPHA - default=false)
|
||||
LoggingAlphaOptions=true|false (ALPHA - default=false)
|
||||
LoggingBetaOptions=true|false (BETA - default=true)
|
||||
-h, --help help for descheduler
|
||||
--http2-max-streams-per-connection int The limit that the server gives to clients for the maximum number of streams in an HTTP/2 connection. Zero means to use golang's default.
|
||||
--kubeconfig string File with kube configuration. Deprecated, use client-connection-kubeconfig instead.
|
||||
@@ -57,6 +60,7 @@ descheduler [flags]
|
||||
--tls-cipher-suites strings Comma-separated list of cipher suites for the server. If omitted, the default Go cipher suites will be used.
|
||||
Preferred values: TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256.
|
||||
Insecure values: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_3DES_EDE_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_RC4_128_SHA.
|
||||
--tls-curve-preferences int32Slice Comma-separated list of numeric Go crypto/tls CurveID values, as the allowed key exchange mechanisms for the server. The supported values depend on the Go version used. See https://pkg.go.dev/crypto/tls#CurveID for values supported for each Go version. The order of the list is ignored, and key exchange mechanisms are chosen by Go from this list using an internal preference order. If omitted, the default Go curves will be used. (default [])
|
||||
--tls-min-version string Minimum TLS version supported. Possible values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13
|
||||
--tls-private-key-file string File containing the default x509 private key matching --tls-cert-file.
|
||||
--tls-sni-cert-key namedCertKey A pair of x509 certificate and private key file paths, optionally suffixed with a list of domain patterns which are fully qualified domain names, possibly with prefixed wildcard segments. The domain patterns also allow IP addresses, but IPs should only be used if the apiserver has visibility to the IP address requested by a client. If no domain patterns are provided, the names of the certificate are extracted. Non-wildcard matches trump over wildcard matches, explicit domain patterns trump over extracted names. For multiple key/certificate pairs, use the --tls-sni-cert-key multiple times. Examples: "example.crt,example.key" or "foo.crt,foo.key:*.foo.com,foo.com". (default [])
|
||||
|
||||
@@ -29,6 +29,25 @@ View all CLI options.
|
||||
```
|
||||
|
||||
## Run Tests
|
||||
|
||||
### All-in-one e2e (kind cluster created by the test script)
|
||||
|
||||
```
|
||||
KIND_E2E=1 make test-e2e
|
||||
```
|
||||
|
||||
Optional skip flags (set to any non-empty value to skip that step):
|
||||
|
||||
| Variable | Skips |
|
||||
|----------|-------|
|
||||
| `SKIP_INSTALL` | kind node-image build and cluster create |
|
||||
| `SKIP_KUBECTL_INSTALL` | kubectl download |
|
||||
| `SKIP_KIND_INSTALL` | kind binary download |
|
||||
| `SKIP_KUBEVIRT_INSTALL` | KubeVirt operator install |
|
||||
| `SKIP_METRICS_SERVER_INSTALL` | metrics-server install |
|
||||
|
||||
### Manual kind cluster (iterative development)
|
||||
|
||||
```
|
||||
GOOS=linux make dev-image
|
||||
make kind-multi-node
|
||||
|
||||
@@ -4,6 +4,7 @@ Starting with descheduler release v0.10.0 container images are available in the
|
||||
|
||||
Descheduler Version | Container Image | Architectures |
|
||||
------------------- |-------------------------------------------------|-------------------------|
|
||||
v0.36.0 | registry.k8s.io/descheduler/descheduler:v0.36.0 | AMD64<br>ARM64<br>ARMv7 |
|
||||
v0.35.0 | registry.k8s.io/descheduler/descheduler:v0.35.0 | AMD64<br>ARM64<br>ARMv7 |
|
||||
v0.34.0 | registry.k8s.io/descheduler/descheduler:v0.34.0 | AMD64<br>ARM64<br>ARMv7 |
|
||||
v0.33.0 | registry.k8s.io/descheduler/descheduler:v0.33.0 | AMD64<br>ARM64<br>ARMv7 |
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: "descheduler/v1alpha2"
|
||||
kind: "DeschedulerPolicy"
|
||||
profiles:
|
||||
- name: ProfileName
|
||||
pluginConfig:
|
||||
- name: "PodLifeTime"
|
||||
args:
|
||||
states:
|
||||
- "Succeeded"
|
||||
conditions:
|
||||
- reason: "PodCompleted"
|
||||
status: "True"
|
||||
minTimeSinceLastTransitionSeconds: 14400 # 4 hours
|
||||
namespaces:
|
||||
include:
|
||||
- "default"
|
||||
plugins:
|
||||
deschedule:
|
||||
enabled:
|
||||
- "PodLifeTime"
|
||||
@@ -1,10 +1,8 @@
|
||||
module sigs.k8s.io/descheduler
|
||||
|
||||
go 1.25.0
|
||||
go 1.26.0
|
||||
|
||||
toolchain go1.25.1
|
||||
|
||||
godebug default=go1.25
|
||||
godebug default=go1.26
|
||||
|
||||
require (
|
||||
github.com/client9/misspell v0.3.4
|
||||
@@ -13,48 +11,47 @@ require (
|
||||
github.com/prometheus/common v0.67.5
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
go.opentelemetry.io/otel v1.40.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0
|
||||
go.opentelemetry.io/otel/sdk v1.38.0
|
||||
go.opentelemetry.io/otel/trace v1.40.0
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||
google.golang.org/grpc v1.78.0
|
||||
k8s.io/api v0.35.1
|
||||
k8s.io/apimachinery v0.35.1
|
||||
k8s.io/apiserver v0.35.1
|
||||
k8s.io/client-go v0.35.1
|
||||
k8s.io/code-generator v0.35.1
|
||||
k8s.io/component-base v0.35.1
|
||||
k8s.io/component-helpers v0.35.1
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
k8s.io/metrics v0.35.1
|
||||
k8s.io/utils v0.0.0-20260108192941-914a6e750570
|
||||
kubevirt.io/api v1.3.0
|
||||
kubevirt.io/client-go v1.3.0
|
||||
go.opentelemetry.io/otel v1.43.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0
|
||||
go.opentelemetry.io/otel/sdk v1.43.0
|
||||
go.opentelemetry.io/otel/trace v1.43.0
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93
|
||||
google.golang.org/grpc v1.82.1
|
||||
k8s.io/api v0.36.1
|
||||
k8s.io/apimachinery v0.36.1
|
||||
k8s.io/apiserver v0.36.1
|
||||
k8s.io/client-go v0.36.1
|
||||
k8s.io/code-generator v0.36.1
|
||||
k8s.io/component-base v0.36.1
|
||||
k8s.io/component-helpers v0.36.1
|
||||
k8s.io/klog/v2 v2.140.0
|
||||
k8s.io/metrics v0.36.1
|
||||
k8s.io/utils v0.0.0-20260507154919-ff6756f316d2
|
||||
kubevirt.io/api v1.9.0
|
||||
kubevirt.io/client-go v1.9.0
|
||||
sigs.k8s.io/mdtoc v1.4.0
|
||||
sigs.k8s.io/yaml v1.6.0
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.24.0 // indirect
|
||||
cel.dev/expr v0.25.1 // indirect
|
||||
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.7.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/go-kit/kit v0.13.0 // indirect
|
||||
github.com/go-kit/log v0.2.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
@@ -62,78 +59,84 @@ require (
|
||||
github.com/go-logr/zapr v1.3.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-openapi/swag v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/cmdutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/conv v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/fileutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/loading v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/mangling v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/netutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||
github.com/golang/glog v1.2.5 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/cel-go v0.26.0 // indirect
|
||||
github.com/google/cel-go v0.30.0 // indirect
|
||||
github.com/google/gnostic-models v0.7.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/jpillora/backoff v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mmarkdown/mmark v2.0.40+incompatible // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
||||
github.com/openshift/api v0.0.0-20230503133300-8bbcb7ca7183 // indirect
|
||||
github.com/openshift/custom-resource-status v1.1.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/stoewer/go-strcase v1.3.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.6.5 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.6.5 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.6.8 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.8 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.6.8 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.uber.org/zap v1.27.1 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.47.0 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/oauth2 v0.34.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/term v0.39.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
golang.org/x/crypto v0.54.0 // indirect
|
||||
golang.org/x/mod v0.38.0 // indirect
|
||||
golang.org/x/net v0.57.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sync v0.22.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/term v0.45.0 // indirect
|
||||
golang.org/x/text v0.40.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.48.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.30.0 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.34.3 // indirect
|
||||
k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect
|
||||
k8s.io/kms v0.35.1 // indirect
|
||||
k8s.io/kube-openapi v0.30.0 // indirect
|
||||
kubevirt.io/containerized-data-importer-api v1.57.0-alpha1 // indirect
|
||||
kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
|
||||
k8s.io/kms v0.36.1 // indirect
|
||||
k8s.io/kube-openapi v0.31.0 // indirect
|
||||
k8s.io/streaming v0.36.1 // indirect
|
||||
kubevirt.io/containerized-data-importer-api v1.64.0 // indirect
|
||||
kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/release-utils v0.8.3 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect
|
||||
)
|
||||
|
||||
replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4
|
||||
replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20260512234627-ef417d054102
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
|
||||
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
|
||||
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
||||
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
@@ -20,23 +18,17 @@ github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F9
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
|
||||
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ=
|
||||
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w=
|
||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
|
||||
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
@@ -50,9 +42,8 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
|
||||
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
|
||||
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
@@ -64,8 +55,6 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
|
||||
github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
|
||||
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
|
||||
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||
@@ -74,8 +63,6 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7
|
||||
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
@@ -90,31 +77,52 @@ github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
|
||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||
github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU=
|
||||
github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ=
|
||||
github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4=
|
||||
github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
|
||||
github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4=
|
||||
github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU=
|
||||
github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y=
|
||||
github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk=
|
||||
github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=
|
||||
github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM=
|
||||
github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s=
|
||||
github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE=
|
||||
github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48=
|
||||
github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg=
|
||||
github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0=
|
||||
github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg=
|
||||
github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8=
|
||||
github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=
|
||||
github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw=
|
||||
github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
|
||||
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
|
||||
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I=
|
||||
github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -127,15 +135,14 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab h1:VYNivV7P8IRHUam2swVUNkhIdp0LRRFKe4hXNnoZKTc=
|
||||
github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI=
|
||||
github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
|
||||
github.com/google/cel-go v0.30.0 h1:ll54AkzKunWkBn9wSoiUXbFZXYZTkdJGNXTBXUoolGo=
|
||||
github.com/google/cel-go v0.30.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8=
|
||||
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
|
||||
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
@@ -143,7 +150,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
@@ -151,10 +157,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -162,23 +166,18 @@ github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97Dwqy
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
@@ -200,11 +199,9 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mmarkdown/mmark v2.0.40+incompatible h1:vMeUeDzBK3H+/mU0oMVfMuhSXJlIA+DE/DMPQNAj5C4=
|
||||
github.com/mmarkdown/mmark v2.0.40+incompatible/go.mod h1:Uvmoz7tvsWpr7bMVxIpqZPyN3FbOtzDmnsJDFp7ltJs=
|
||||
@@ -230,54 +227,16 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k
|
||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=
|
||||
github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
|
||||
github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0=
|
||||
github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=
|
||||
github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw=
|
||||
github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo=
|
||||
github.com/onsi/ginkgo/v2 v2.8.1/go.mod h1:N1/NbDngAFcSLdyZ+/aYTYGSlq9qMCS/cNKGJjy+csc=
|
||||
github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk=
|
||||
github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo=
|
||||
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
|
||||
github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
|
||||
github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
|
||||
github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||
github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
|
||||
github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=
|
||||
github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM=
|
||||
github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
|
||||
github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM=
|
||||
github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
|
||||
github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw=
|
||||
github.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw=
|
||||
github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ=
|
||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
||||
github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4=
|
||||
github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ=
|
||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY=
|
||||
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
|
||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||
github.com/openshift/api v0.0.0-20230503133300-8bbcb7ca7183 h1:t/CahSnpqY46sQR01SoS+Jt0jtjgmhgE6lFmRnO4q70=
|
||||
github.com/openshift/api v0.0.0-20230503133300-8bbcb7ca7183/go.mod h1:4VWG+W22wrB4HfBL88P40DxLEpSOaiBVxUnfalfJo9k=
|
||||
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
|
||||
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
github.com/openshift/custom-resource-status v1.1.2 h1:C3DL44LEbvlbItfd8mT5jWrqPfHnSOQoQf/sypqA6A4=
|
||||
github.com/openshift/custom-resource-status v1.1.2/go.mod h1:DB/Mf2oTeiAmVVX1gN+NEqweonAPY0TKUwADizj8+ZA=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@@ -290,10 +249,9 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
|
||||
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
||||
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
@@ -310,8 +268,6 @@ github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
|
||||
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
@@ -341,46 +297,46 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.etcd.io/etcd/api/v3 v3.6.5 h1:pMMc42276sgR1j1raO/Qv3QI9Af/AuyQUW6CBAWuntA=
|
||||
go.etcd.io/etcd/api/v3 v3.6.5/go.mod h1:ob0/oWA/UQQlT1BmaEkWQzI0sJ1M0Et0mMpaABxguOQ=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5 h1:Duz9fAzIZFhYWgRjp/FgNq2gO1jId9Yae/rLn3RrBP8=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5/go.mod h1:8Wx3eGRPiy0qOFMZT/hfvdos+DjEaPxdIDiCDUv/FQk=
|
||||
go.etcd.io/etcd/client/v3 v3.6.5 h1:yRwZNFBx/35VKHTcLDeO7XVLbCBFbPi+XV4OC3QJf2U=
|
||||
go.etcd.io/etcd/client/v3 v3.6.5/go.mod h1:ZqwG/7TAFZ0BJ0jXRPoJjKQJtbFo/9NIY8uoFFKcCyo=
|
||||
go.etcd.io/etcd/pkg/v3 v3.6.5 h1:byxWB4AqIKI4SBmquZUG1WGtvMfMaorXFoCcFbVeoxM=
|
||||
go.etcd.io/etcd/pkg/v3 v3.6.5/go.mod h1:uqrXrzmMIJDEy5j00bCqhVLzR5jEJIwDp5wTlLwPGOU=
|
||||
go.etcd.io/etcd/server/v3 v3.6.5 h1:4RbUb1Bd4y1WkBHmuF+cZII83JNQMuNXzyjwigQ06y0=
|
||||
go.etcd.io/etcd/server/v3 v3.6.5/go.mod h1:PLuhyVXz8WWRhzXDsl3A3zv/+aK9e4A9lpQkqawIaH0=
|
||||
go.etcd.io/etcd/api/v3 v3.6.8 h1:gqb1VN92TAI6G2FiBvWcqKtHiIjr4SU2GdXxTwyexbM=
|
||||
go.etcd.io/etcd/api/v3 v3.6.8/go.mod h1:qyQj1HZPUV3B5cbAL8scG62+fyz5dSxxu0w8pn28N6Q=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.8 h1:Qs/5C0LNFiqXxYf2GU8MVjYUEXJ6sZaYOz0zEqQgy50=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.8/go.mod h1:GsiTRUZE2318PggZkAo6sWb6l8JLVrnckTNfbG8PWtw=
|
||||
go.etcd.io/etcd/client/v3 v3.6.8 h1:B3G76t1UykqAOrbio7s/EPatixQDkQBevN8/mwiplrY=
|
||||
go.etcd.io/etcd/client/v3 v3.6.8/go.mod h1:MVG4BpSIuumPi+ELF7wYtySETmoTWBHVcDoHdVupwt8=
|
||||
go.etcd.io/etcd/pkg/v3 v3.6.8 h1:Xe+LIL974spy8b4nEx3H0KMr1ofq3r0kh6FbU3aw4es=
|
||||
go.etcd.io/etcd/pkg/v3 v3.6.8/go.mod h1:TRibVNe+FqJIe1abOAA1PsuQ4wqO87ZaOoprg09Tn8c=
|
||||
go.etcd.io/etcd/server/v3 v3.6.8 h1:U2strdSEy1U8qcSzRIdkYpvOPtBy/9i/IfaaCI9flZ4=
|
||||
go.etcd.io/etcd/server/v3 v3.6.8/go.mod h1:88dCtwUnSirkUoJbflQxxWXqtBSZa6lSG0Kuej+dois=
|
||||
go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ=
|
||||
go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c=
|
||||
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 h1:XmiuHzgJt067+a6kwyAzkhXooYVv3/TOw9cM2VfJgUM=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0/go.mod h1:KDgtbWKTQs4bM+VPUr6WlL9m/WXcmkCcBlIzqxPGzmI=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
@@ -391,26 +347,26 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
||||
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
@@ -418,23 +374,25 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
|
||||
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -453,36 +411,30 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
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.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
||||
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -491,16 +443,19 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -522,86 +477,81 @@ golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.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.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
|
||||
golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4=
|
||||
golang.org/x/telemetry v0.0.0-20250807160809-1a19826ec488/go.mod h1:fGb/2+tgXXjhjHsTNdVEEMZNWA0quBnfrO+AfoDSAKw=
|
||||
golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053/go.mod h1:+nZKN+XVh4LCiA9DV3ywrzN4gumyCnKjau3NGb9SGoE=
|
||||
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
|
||||
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
|
||||
golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
|
||||
golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2/go.mod h1:b7fPSJ0pKZ3ccUh8gnTONJxhn3c/PS6tyzQvyqw4iA8=
|
||||
golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4/go.mod h1:g5NllXBEermZrmR51cJDQxmJUHUOfRAaNyWBM+R+548=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@@ -614,28 +564,25 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM=
|
||||
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
||||
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
||||
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
golang.org/x/tools v0.34.1-0.20250613162507-3f93fece84c7/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
|
||||
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
|
||||
golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
||||
@@ -645,23 +592,23 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
|
||||
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -673,11 +620,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -704,27 +649,26 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ=
|
||||
k8s.io/api v0.35.1 h1:0PO/1FhlK/EQNVK5+txc4FuhQibV25VLSdLMmGpDE/Q=
|
||||
k8s.io/api v0.35.1/go.mod h1:28uR9xlXWml9eT0uaGo6y71xK86JBELShLy4wR1XtxM=
|
||||
k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs=
|
||||
k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y=
|
||||
k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY=
|
||||
k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo=
|
||||
k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g=
|
||||
k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0=
|
||||
k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM=
|
||||
k8s.io/apimachinery v0.35.1 h1:yxO6gV555P1YV0SANtnTjXYfiivaTPvCTKX6w6qdDsU=
|
||||
k8s.io/apimachinery v0.35.1/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
|
||||
k8s.io/apiserver v0.35.1 h1:potxdhhTL4i6AYAa2QCwtlhtB1eCdWQFvJV6fXgJzxs=
|
||||
k8s.io/apiserver v0.35.1/go.mod h1:BiL6Dd3A2I/0lBnteXfWmCFobHM39vt5+hJQd7Lbpi4=
|
||||
k8s.io/client-go v0.35.1 h1:+eSfZHwuo/I19PaSxqumjqZ9l5XiTEKbIaJ+j1wLcLM=
|
||||
k8s.io/client-go v0.35.1/go.mod h1:1p1KxDt3a0ruRfc/pG4qT/3oHmUj1AhSHEcxNSGg+OA=
|
||||
k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA=
|
||||
k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8=
|
||||
k8s.io/apiserver v0.36.1 h1:iMS5V+rPUertv5P9RaqJgmHHTuh4quWpoxchvMUY+JY=
|
||||
k8s.io/apiserver v0.36.1/go.mod h1:Cby1PbLWztu0GDOxoO6iFOyyqIsziHNEW+w9zVQ22Kw=
|
||||
k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0=
|
||||
k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU=
|
||||
k8s.io/code-generator v0.23.3/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk=
|
||||
k8s.io/code-generator v0.35.1 h1:yLKR2la7Z9cWT5qmk67ayx8xXLM4RRKQMnC8YPvTWRI=
|
||||
k8s.io/code-generator v0.35.1/go.mod h1:F2Fhm7aA69tC/VkMXLDokdovltXEF026Tb9yfQXQWKg=
|
||||
k8s.io/component-base v0.35.1 h1:XgvpRf4srp037QWfGBLFsYMUQJkE5yMa94UsJU7pmcE=
|
||||
k8s.io/component-base v0.35.1/go.mod h1:HI/6jXlwkiOL5zL9bqA3en1Ygv60F03oEpnuU1G56Bs=
|
||||
k8s.io/component-helpers v0.35.1 h1:vwQ/cAfnVwaPeSXTu4DdK3d3n11Lugc5vMb6EV809ZY=
|
||||
k8s.io/component-helpers v0.35.1/go.mod h1:HQqMwUk68Yyxgj92dJ+J1w/qbx9M0QR0eZ680m/o+Rk=
|
||||
k8s.io/code-generator v0.36.1 h1:5bHQ7NbBcFFLHcoyo/hgU3m2tQV5RLz2nv4QNDlsbXc=
|
||||
k8s.io/code-generator v0.36.1/go.mod h1:oCv8WmrW2RGdcMyvSk1aYbBfSs51ggtSFQr1YNeuAuo=
|
||||
k8s.io/component-base v0.36.1 h1:iG6GsELftXqTNG9HG6kiVjatSgAw1sf5pJ6R5a6N0kA=
|
||||
k8s.io/component-base v0.36.1/go.mod h1:nf9XPlntRdqO6WMeEWAA5F93Y4ICZQdeT9GeqLDB3JI=
|
||||
k8s.io/component-helpers v0.36.1 h1:BTrr5fzNSm8TkQfXrKT3N9ioWwiC4n2FTIwGTUo/ccg=
|
||||
k8s.io/component-helpers v0.36.1/go.mod h1:s38HnzKQRurbUnhI5IV8GwyL/a3lVuNCYZMTd+rITMM=
|
||||
k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
|
||||
k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
|
||||
k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
|
||||
k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYoJBnnUAT5MHlTkbjhQ=
|
||||
k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b/go.mod h1:CgujABENc3KuTrcsdpGmrrASjtQsWCT7R99mEV4U/fM=
|
||||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
||||
@@ -732,30 +676,31 @@ k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
|
||||
k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kms v0.35.1 h1:kjv2r9g1mY7uL+l1RhyAZvWVZIA/4qIfBHXyjFGLRhU=
|
||||
k8s.io/kms v0.35.1/go.mod h1:VT+4ekZAdrZDMgShK37vvlyHUVhwI9t/9tvh0AyCWmQ=
|
||||
k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4 h1:HhDfevmPS+OalTjQRKbTHppRIz01AWi8s45TMXStgYY=
|
||||
k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
|
||||
k8s.io/metrics v0.35.1 h1:MUcrUcWlq81XiripkydzCGsY9zQawDXfP9IICNNcVVw=
|
||||
k8s.io/metrics v0.35.1/go.mod h1:9x7xWOAOiWzHA0vaqLgSE4PXF3vyT5ts5XIbx8OSjiI=
|
||||
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
|
||||
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
|
||||
k8s.io/kms v0.36.1 h1:XdvKpywoW4k7YUHDh5uYP4mahJXECswHGfCddBBYLZs=
|
||||
k8s.io/kms v0.36.1/go.mod h1:g91diTD9h0oJCCHkTb00krlF+Qm5HTnkWLi9Q/TpRoc=
|
||||
k8s.io/kube-openapi v0.0.0-20260512234627-ef417d054102 h1:xs2ux1MvyrOdfKwS3vuFWrGuLgDOHk6id975Twx2Jss=
|
||||
k8s.io/kube-openapi v0.0.0-20260512234627-ef417d054102/go.mod h1:V/QaCUYDa+0QpcHhVVc5l99Uz56wEMEXBSj9oCDkNDY=
|
||||
k8s.io/metrics v0.36.1 h1:MQPb+G4RhrKEpt8NETPssbW8QgGUc4Jbqu1jx+kPqGk=
|
||||
k8s.io/metrics v0.36.1/go.mod h1:xqS8XcWLjDzo6E7DJm/GfjKpRKdN5/MtJAQFuV6nLUc=
|
||||
k8s.io/streaming v0.36.1 h1:L+K68n4Gg940BGNNYtUBvL1WTLL0YnKT3s+P1MNAmR4=
|
||||
k8s.io/streaming v0.36.1/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s=
|
||||
k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
k8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY=
|
||||
k8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||
kubevirt.io/api v1.3.0 h1:9sGElMmnRU50pGED+MPPD2OwQl4S5lvjCUjm+t0mI90=
|
||||
kubevirt.io/api v1.3.0/go.mod h1:e6LkElYZZm8NcP2gKlFVHZS9pgNhIARHIjSBSfeiP1s=
|
||||
kubevirt.io/client-go v1.3.0 h1:/HKn4exzwsctEVTwVtEFaeT9D2v4TgWr2SmxITVEZ/4=
|
||||
kubevirt.io/client-go v1.3.0/go.mod h1:qmcJZvUjbmggY1pp7irO3zesBJj7wwGIWAdnYEoh3yc=
|
||||
kubevirt.io/containerized-data-importer-api v1.57.0-alpha1 h1:IWo12+ei3jltSN5jQN1xjgakfvRSF3G3Rr4GXVOOy2I=
|
||||
kubevirt.io/containerized-data-importer-api v1.57.0-alpha1/go.mod h1:Y/8ETgHS1GjO89bl682DPtQOYEU/1ctPFBz6Sjxm4DM=
|
||||
kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 h1:QMrd0nKP0BGbnxTqakhDZAUhGKxPiPiN5gSDqKUmGGc=
|
||||
kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90/go.mod h1:018lASpFYBsYN6XwmA2TIrPCx6e0gviTd/ZNtSitKgc=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||
k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 h1:wU4tMEhLGgIbLvXQb1cfN+EcM0wf7zC6CPF+C79jroc=
|
||||
k8s.io/utils v0.0.0-20260507154919-ff6756f316d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||
kubevirt.io/api v1.9.0 h1:FWqx9yGk/GHUP5o4IthGXZ8FeAlgVNv2It48wD86UUo=
|
||||
kubevirt.io/api v1.9.0/go.mod h1:tFKAVaJMm7bXCMLxOoUdAqBprKAXoCa0sp0gBmnfBgY=
|
||||
kubevirt.io/client-go v1.9.0 h1:ksR37OV0Zft6g/9YPFEjevlkRpTvWuyVE44zj9xGuGQ=
|
||||
kubevirt.io/client-go v1.9.0/go.mod h1:cgGTWTqnghJdyiGXx4y9KXAHholmF2BwodUKXuQa9Ns=
|
||||
kubevirt.io/containerized-data-importer-api v1.64.0 h1:yBLY6qEogUp8F+XSJvabIkx9uEEDeToYDfRr0mbUxJc=
|
||||
kubevirt.io/containerized-data-importer-api v1.64.0/go.mod h1:VGp35wxpLXU18b7cnEpmcThI3AjcZUSfg/Zfql44U4o=
|
||||
kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4 h1:fZYvD3/Vnitfkx6IJxjLAk8ugnZQ7CXVYcRfkSKmuZY=
|
||||
kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4/go.mod h1:018lASpFYBsYN6XwmA2TIrPCx6e0gviTd/ZNtSitKgc=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
|
||||
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/mdtoc v1.4.0 h1:2pDEwJSjoVrGr5BPkG+LoLkYLKvgtGYurrBY8ul3SxQ=
|
||||
@@ -766,11 +711,10 @@ sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxO
|
||||
sigs.k8s.io/release-utils v0.8.3 h1:KtOtA4qDmzJyeQ2zkDsFVI25+NViwms/o5eL2NftFdA=
|
||||
sigs.k8s.io/release-utils v0.8.3/go.mod h1:fp82Fma06OXBhEJ+GUJKqvcplDBomruK1R/1fWJnsrQ=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.2.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.4.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Used by e2e tests (test/run-e2e-tests.sh) and make kind-multi-node.
|
||||
# Worker nodes carry topology zone labels required by topology spread constraint e2e tests.
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
nodes:
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
go::verify_version() {
|
||||
GO_VERSION=($(go version))
|
||||
|
||||
if [[ -z $(echo "${GO_VERSION[2]}" | grep -E 'go1.23|go1.24|go1.25') ]]; then
|
||||
if [[ -z $(echo "${GO_VERSION[2]}" | grep -E 'go1.24|go1.25|go1.26') ]]; then
|
||||
echo "Unknown go version '${GO_VERSION[2]}', skipping gofmt."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -15,7 +15,7 @@ rules:
|
||||
verbs: ["get", "watch", "list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "watch", "list", "delete"]
|
||||
verbs: ["get", "watch", "list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods/eviction"]
|
||||
verbs: ["create"]
|
||||
|
||||
@@ -16,7 +16,7 @@ spec:
|
||||
priorityClassName: system-cluster-critical
|
||||
containers:
|
||||
- name: descheduler
|
||||
image: registry.k8s.io/descheduler/descheduler:v0.35.0
|
||||
image: registry.k8s.io/descheduler/descheduler:v0.36.0
|
||||
volumeMounts:
|
||||
- mountPath: /policy-dir
|
||||
name: policy-volume
|
||||
|
||||
@@ -19,7 +19,7 @@ spec:
|
||||
serviceAccountName: descheduler-sa
|
||||
containers:
|
||||
- name: descheduler
|
||||
image: registry.k8s.io/descheduler/descheduler:v0.35.0
|
||||
image: registry.k8s.io/descheduler/descheduler:v0.36.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- "/bin/descheduler"
|
||||
|
||||
@@ -14,7 +14,7 @@ spec:
|
||||
priorityClassName: system-cluster-critical
|
||||
containers:
|
||||
- name: descheduler
|
||||
image: registry.k8s.io/descheduler/descheduler:v0.35.0
|
||||
image: registry.k8s.io/descheduler/descheduler:v0.36.0
|
||||
volumeMounts:
|
||||
- mountPath: /policy-dir
|
||||
name: policy-volume
|
||||
|
||||
@@ -235,8 +235,10 @@ func initDescheduler(t *testing.T, ctx context.Context, featureGates featuregate
|
||||
if dryRun {
|
||||
if err := wait.PollUntilContextTimeout(ctx, 100*time.Millisecond, 5*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
for _, obj := range objects {
|
||||
// Only check for nodes - secrets are handled by namespacedSharedInformerFactory
|
||||
if _, ok := obj.(*v1.Node); !ok {
|
||||
// Only check for nodes and pods - secrets are handled by namespacedSharedInformerFactory
|
||||
switch obj.(type) {
|
||||
case *v1.Node, *v1.Pod:
|
||||
default:
|
||||
continue
|
||||
}
|
||||
exists, err := descheduler.kubeClientSandbox.hasRuntimeObjectInIndexer(obj)
|
||||
@@ -256,7 +258,7 @@ func initDescheduler(t *testing.T, ctx context.Context, featureGates featuregate
|
||||
}
|
||||
return true, nil
|
||||
}); err != nil {
|
||||
t.Fatalf("nodes did not propagate to the indexer: %v", err)
|
||||
t.Fatalf("objects did not propagate to the indexer: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ var (
|
||||
|
||||
type evictionRequestItem struct {
|
||||
podName, podNamespace, podNodeName string
|
||||
strategyName, profileName string
|
||||
evictionAssumed bool
|
||||
assumedTimestamp metav1.Time
|
||||
}
|
||||
@@ -68,6 +69,7 @@ type evictionRequestsCache struct {
|
||||
requestsPerNamespace map[string]uint
|
||||
requestsTotal uint
|
||||
assumedRequestTimeoutSeconds uint
|
||||
onAssumedTimeout func(item evictionRequestItem)
|
||||
}
|
||||
|
||||
func newEvictionRequestsCache(assumedRequestTimeoutSeconds uint) *evictionRequestsCache {
|
||||
@@ -94,6 +96,9 @@ func (erc *evictionRequestsCache) cleanCache(ctx context.Context) {
|
||||
requestAgeSeconds := uint(metav1.Now().Sub(item.assumedTimestamp.Local()).Seconds())
|
||||
if requestAgeSeconds > erc.assumedRequestTimeoutSeconds {
|
||||
klog.V(4).InfoS("Assumed eviction request in background timed out, deleting", "timeout", erc.assumedRequestTimeoutSeconds, "podNamespace", item.podNamespace, "podName", item.podName)
|
||||
if erc.onAssumedTimeout != nil {
|
||||
erc.onAssumedTimeout(item)
|
||||
}
|
||||
erc.deleteItem(uid)
|
||||
}
|
||||
}
|
||||
@@ -149,17 +154,35 @@ func (erc *evictionRequestsCache) addPod(pod *v1.Pod) {
|
||||
erc.requestsTotal++
|
||||
}
|
||||
|
||||
func (erc *evictionRequestsCache) assumePod(pod *v1.Pod) {
|
||||
func (erc *evictionRequestsCache) assumePod(pod *v1.Pod, profileName, strategyName string) {
|
||||
erc.mu.Lock()
|
||||
defer erc.mu.Unlock()
|
||||
uid := getPodKey(pod)
|
||||
if _, exists := erc.requests[uid]; exists {
|
||||
if item, exists := erc.requests[uid]; exists {
|
||||
if item.evictionAssumed {
|
||||
// Pod already assumed by a previous strategy/profile; first one wins.
|
||||
return
|
||||
}
|
||||
// The informer's UpdateFunc called addPod (evictionAssumed=false) before
|
||||
// the TooManyRequests response arrived. Upgrade the entry in place without
|
||||
// bumping the counters a second time.
|
||||
erc.requests[uid] = evictionRequestItem{
|
||||
podNamespace: item.podNamespace,
|
||||
podName: item.podName,
|
||||
podNodeName: item.podNodeName,
|
||||
strategyName: strategyName,
|
||||
profileName: profileName,
|
||||
evictionAssumed: true,
|
||||
assumedTimestamp: metav1.NewTime(time.Now()),
|
||||
}
|
||||
return
|
||||
}
|
||||
erc.requests[uid] = evictionRequestItem{
|
||||
podNamespace: pod.Namespace,
|
||||
podName: pod.Name,
|
||||
podNodeName: pod.Spec.NodeName,
|
||||
strategyName: strategyName,
|
||||
profileName: profileName,
|
||||
evictionAssumed: true,
|
||||
assumedTimestamp: metav1.NewTime(time.Now()),
|
||||
}
|
||||
@@ -191,6 +214,14 @@ func (erc *evictionRequestsCache) deletePod(pod *v1.Pod) {
|
||||
}
|
||||
}
|
||||
|
||||
func (erc *evictionRequestsCache) getPod(pod *v1.Pod) (evictionRequestItem, bool) {
|
||||
erc.mu.RLock()
|
||||
defer erc.mu.RUnlock()
|
||||
uid := getPodKey(pod)
|
||||
item, exists := erc.requests[uid]
|
||||
return item, exists
|
||||
}
|
||||
|
||||
func (erc *evictionRequestsCache) hasPod(pod *v1.Pod) bool {
|
||||
erc.mu.RLock()
|
||||
defer erc.mu.RUnlock()
|
||||
@@ -263,6 +294,12 @@ func NewPodEvictor(
|
||||
|
||||
if featureGates.Enabled(features.EvictionsInBackground) {
|
||||
erCache := newEvictionRequestsCache(assumedEvictionRequestTimeoutSeconds)
|
||||
if podEvictor.metricsEnabled {
|
||||
erCache.onAssumedTimeout = func(item evictionRequestItem) {
|
||||
metrics.PodsEvicted.With(map[string]string{"result": "error", "strategy": item.strategyName, "namespace": item.podNamespace, "node": item.podNodeName, "profile": item.profileName}).Inc()
|
||||
metrics.PodsEvictedTotal.With(map[string]string{"result": "error", "strategy": item.strategyName, "namespace": item.podNamespace, "node": item.podNodeName, "profile": item.profileName}).Inc()
|
||||
}
|
||||
}
|
||||
|
||||
handlerRegistration, err := podInformer.AddEventHandler(
|
||||
cache.ResourceEventHandlerFuncs{
|
||||
@@ -303,8 +340,18 @@ func NewPodEvictor(
|
||||
}
|
||||
// Remove completed/suceeeded or failed pods from the cache
|
||||
if newPod.Status.Phase == v1.PodSucceeded || newPod.Status.Phase == v1.PodFailed {
|
||||
klog.V(3).InfoS("Pod with eviction in background completed. Removing pod from the cache.", "pod", klog.KObj(newPod))
|
||||
erCache.deletePod(newPod)
|
||||
if item, exists := erCache.getPod(newPod); exists {
|
||||
klog.V(3).InfoS("Pod with eviction in background completed. Removing pod from the cache.", "pod", klog.KObj(newPod))
|
||||
if item.evictionAssumed && podEvictor.metricsEnabled {
|
||||
result := "success"
|
||||
if newPod.Status.Phase == v1.PodFailed {
|
||||
result = "error"
|
||||
}
|
||||
metrics.PodsEvicted.With(map[string]string{"result": result, "strategy": item.strategyName, "namespace": item.podNamespace, "node": item.podNodeName, "profile": item.profileName}).Inc()
|
||||
metrics.PodsEvictedTotal.With(map[string]string{"result": result, "strategy": item.strategyName, "namespace": item.podNamespace, "node": item.podNodeName, "profile": item.profileName}).Inc()
|
||||
}
|
||||
erCache.deletePod(newPod)
|
||||
}
|
||||
return
|
||||
}
|
||||
// Ignore any pod that does not have eviction in progress
|
||||
@@ -347,8 +394,12 @@ func NewPodEvictor(
|
||||
klog.ErrorS(nil, "Cannot convert to *v1.Pod", "obj", t)
|
||||
return
|
||||
}
|
||||
if erCache.hasPod(pod) {
|
||||
if item, exists := erCache.getPod(pod); exists {
|
||||
klog.V(3).InfoS("Pod with eviction in background deleted/evicted. Removing pod from the cache.", "pod", klog.KObj(pod))
|
||||
if item.evictionAssumed && podEvictor.metricsEnabled {
|
||||
metrics.PodsEvicted.With(map[string]string{"result": "success", "strategy": item.strategyName, "namespace": item.podNamespace, "node": item.podNodeName, "profile": item.profileName}).Inc()
|
||||
metrics.PodsEvictedTotal.With(map[string]string{"result": "success", "strategy": item.strategyName, "namespace": item.podNamespace, "node": item.podNodeName, "profile": item.profileName}).Inc()
|
||||
}
|
||||
}
|
||||
erCache.deletePod(pod)
|
||||
},
|
||||
@@ -427,12 +478,6 @@ func (pe *PodEvictor) evictionRequestsPerNamespace(ns string) uint {
|
||||
}
|
||||
}
|
||||
|
||||
func (pe *PodEvictor) EvictionRequests(node *v1.Node) uint {
|
||||
pe.mu.RLock()
|
||||
defer pe.mu.RUnlock()
|
||||
return pe.evictionRequestsTotal()
|
||||
}
|
||||
|
||||
func (pe *PodEvictor) TotalEvictionRequests() uint {
|
||||
pe.mu.RLock()
|
||||
defer pe.mu.RUnlock()
|
||||
@@ -608,7 +653,11 @@ func (pe *PodEvictor) evictPod(ctx context.Context, pod *v1.Pod, opts EvictOptio
|
||||
return true, nil
|
||||
}
|
||||
klog.V(3).InfoS("Eviction in background assumed", "pod", klog.KObj(pod))
|
||||
pe.erCache.assumePod(pod)
|
||||
pe.erCache.assumePod(pod, opts.ProfileName, opts.StrategyName)
|
||||
if pe.metricsEnabled {
|
||||
metrics.PodsEvicted.With(map[string]string{"result": "background", "strategy": opts.StrategyName, "namespace": pod.Namespace, "node": pod.Spec.NodeName, "profile": opts.ProfileName}).Inc()
|
||||
metrics.PodsEvictedTotal.With(map[string]string{"result": "background", "strategy": opts.StrategyName, "namespace": pod.Namespace, "node": pod.Spec.NodeName, "profile": opts.ProfileName}).Inc()
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ import (
|
||||
"k8s.io/klog/v2"
|
||||
utilptr "k8s.io/utils/ptr"
|
||||
|
||||
metricstest "k8s.io/component-base/metrics/testutil"
|
||||
|
||||
deschedulermetrics "sigs.k8s.io/descheduler/metrics"
|
||||
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
|
||||
"sigs.k8s.io/descheduler/pkg/features"
|
||||
"sigs.k8s.io/descheduler/pkg/utils"
|
||||
@@ -463,6 +466,376 @@ func TestEvictionRequestsCacheCleanup(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvictionInBackgroundMetrics(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), wait.ForeverTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
deschedulermetrics.Register()
|
||||
deschedulermetrics.PodsEvicted.Reset()
|
||||
deschedulermetrics.PodsEvictedTotal.Reset()
|
||||
|
||||
node1 := test.BuildTestNode("n1", 2000, 3000, 10, nil)
|
||||
|
||||
ownerRef1 := test.GetReplicaSetOwnerRefList()
|
||||
updatePod := func(pod *v1.Pod) {
|
||||
pod.Namespace = "dev"
|
||||
pod.ObjectMeta.OwnerReferences = ownerRef1
|
||||
}
|
||||
updatePodWithEvictionAnnotation := func(pod *v1.Pod) {
|
||||
updatePod(pod)
|
||||
pod.Annotations = map[string]string{
|
||||
EvictionRequestAnnotationKey: "",
|
||||
}
|
||||
}
|
||||
|
||||
// p1, p2: will be evicted in background
|
||||
// p3, p4: will be evicted normally
|
||||
p1 := test.BuildTestPod("p1", 100, 0, node1.Name, updatePodWithEvictionAnnotation)
|
||||
p2 := test.BuildTestPod("p2", 100, 0, node1.Name, updatePodWithEvictionAnnotation)
|
||||
p3 := test.BuildTestPod("p3", 100, 0, node1.Name, updatePod)
|
||||
p4 := test.BuildTestPod("p4", 100, 0, node1.Name, updatePod)
|
||||
|
||||
client := fakeclientset.NewSimpleClientset(node1, p1, p2, p3, p4)
|
||||
sharedInformerFactory := informers.NewSharedInformerFactory(client, 0)
|
||||
_, eventRecorder := utils.GetRecorderAndBroadcaster(ctx, client)
|
||||
|
||||
podEvictor, err := NewPodEvictor(
|
||||
ctx,
|
||||
client,
|
||||
eventRecorder,
|
||||
sharedInformerFactory.Core().V1().Pods().Informer(),
|
||||
initFeatureGates(),
|
||||
NewOptions().WithMetricsEnabled(true),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error when creating a pod evictor: %v", err)
|
||||
}
|
||||
|
||||
client.PrependReactor("create", "pods", func(action core.Action) (bool, runtime.Object, error) {
|
||||
if action.GetSubresource() == "eviction" {
|
||||
createAct, matched := action.(core.CreateActionImpl)
|
||||
if !matched {
|
||||
return false, nil, fmt.Errorf("unable to convert action to core.CreateActionImpl")
|
||||
}
|
||||
if eviction, matched := createAct.Object.(*policy.Eviction); matched {
|
||||
if eviction.GetName() == "p1" || eviction.GetName() == "p2" {
|
||||
return true, nil, &apierrors.StatusError{
|
||||
ErrStatus: metav1.Status{
|
||||
Reason: metav1.StatusReasonTooManyRequests,
|
||||
Message: "Eviction triggered evacuation",
|
||||
},
|
||||
}
|
||||
}
|
||||
return true, nil, nil
|
||||
}
|
||||
}
|
||||
return false, nil, nil
|
||||
})
|
||||
|
||||
sharedInformerFactory.Start(ctx.Done())
|
||||
sharedInformerFactory.WaitForCacheSync(ctx.Done())
|
||||
|
||||
evictOpts := EvictOptions{StrategyName: "TestStrategy", ProfileName: "TestProfile"}
|
||||
|
||||
// Cycle 1: p1/p2 go to background, p3/p4 are evicted normally.
|
||||
podEvictor.EvictPod(ctx, p1, evictOpts)
|
||||
podEvictor.EvictPod(ctx, p2, evictOpts)
|
||||
podEvictor.EvictPod(ctx, p3, evictOpts)
|
||||
podEvictor.EvictPod(ctx, p4, evictOpts)
|
||||
|
||||
// After cycle 1: 2 "background" (p1, p2), 2 "success" (p3, p4).
|
||||
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "background"}, 2)
|
||||
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "success"}, 2)
|
||||
|
||||
// Cycle 2: p1/p2 are still running (background eviction in progress).
|
||||
// Re-attempting them must not emit any additional metrics.
|
||||
podEvictor.EvictPod(ctx, p1, evictOpts)
|
||||
podEvictor.EvictPod(ctx, p2, evictOpts)
|
||||
|
||||
// Counts must be unchanged after cycle 2.
|
||||
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "background"}, 2)
|
||||
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "success"}, 2)
|
||||
|
||||
// The background evictions complete: p1 and p2 are deleted.
|
||||
// The informer's DeleteFunc fires and emits a "success" metric for each assumed pod.
|
||||
client.CoreV1().Pods(p1.Namespace).Delete(ctx, p1.Name, metav1.DeleteOptions{})
|
||||
client.CoreV1().Pods(p2.Namespace).Delete(ctx, p2.Name, metav1.DeleteOptions{})
|
||||
|
||||
// Poll until DeleteFunc has fired for both pods (informer is async).
|
||||
if err := wait.PollUntilContextTimeout(ctx, 100*time.Millisecond, wait.ForeverTestTimeout, true, func(ctx context.Context) (bool, error) {
|
||||
return !podEvictor.erCache.hasPod(p1) && !podEvictor.erCache.hasPod(p2), nil
|
||||
}); err != nil {
|
||||
t.Fatalf("Timed out waiting for background evictions to complete: %v", err)
|
||||
}
|
||||
|
||||
// After deletion: "success" grows by 2 (p1, p2 completed), "background" stays at 2.
|
||||
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "success"}, 4)
|
||||
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "background"}, 2)
|
||||
}
|
||||
|
||||
// TestEvictionInBackgroundMetrics_InformerRace reproduces the race where the
|
||||
// informer's UpdateFunc calls addPod (evictionAssumed=false) before evictPod's
|
||||
// assumePod call, causing the "success" metric to be silently dropped when the
|
||||
// pod is later deleted.
|
||||
//
|
||||
// The race: evictPod sends the eviction request; the KubeVirt handler sets
|
||||
// EvictionInProgressAnnotationKey before returning TooManyRequests, so the
|
||||
// informer's UpdateFunc fires and calls addPod (evictionAssumed=false) while
|
||||
// evictPod is still waiting for the HTTP response. When the response arrives,
|
||||
// assumePod finds the entry already present and returns early, leaving
|
||||
// evictionAssumed=false. DeleteFunc then skips the "success" metric.
|
||||
func TestEvictionInBackgroundMetrics_InformerRace(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), wait.ForeverTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
deschedulermetrics.Register()
|
||||
deschedulermetrics.PodsEvicted.Reset()
|
||||
deschedulermetrics.PodsEvictedTotal.Reset()
|
||||
|
||||
node1 := test.BuildTestNode("n1", 2000, 3000, 10, nil)
|
||||
|
||||
ownerRef1 := test.GetReplicaSetOwnerRefList()
|
||||
p1 := test.BuildTestPod("p1", 100, 0, node1.Name, func(pod *v1.Pod) {
|
||||
pod.Namespace = "dev"
|
||||
pod.ObjectMeta.OwnerReferences = ownerRef1
|
||||
pod.Annotations = map[string]string{
|
||||
EvictionRequestAnnotationKey: "",
|
||||
}
|
||||
})
|
||||
|
||||
client := fakeclientset.NewSimpleClientset(node1, p1)
|
||||
sharedInformerFactory := informers.NewSharedInformerFactory(client, 0)
|
||||
_, eventRecorder := utils.GetRecorderAndBroadcaster(ctx, client)
|
||||
|
||||
podEvictor, err := NewPodEvictor(
|
||||
ctx,
|
||||
client,
|
||||
eventRecorder,
|
||||
sharedInformerFactory.Core().V1().Pods().Informer(),
|
||||
initFeatureGates(),
|
||||
NewOptions().WithMetricsEnabled(true),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error when creating a pod evictor: %v", err)
|
||||
}
|
||||
|
||||
// calling erCache.addPod from a goroutine.
|
||||
triggerAddPod := make(chan struct{})
|
||||
addPodDone := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
<-triggerAddPod
|
||||
// Simulate what UpdateFunc does when KubeVirt sets
|
||||
// EvictionInProgressAnnotationKey before the TooManyRequests response
|
||||
// arrives: addPod inserts the entry with evictionAssumed=false.
|
||||
podEvictor.erCache.addPod(p1)
|
||||
close(addPodDone)
|
||||
}()
|
||||
|
||||
client.PrependReactor("create", "pods", func(action core.Action) (bool, runtime.Object, error) {
|
||||
if action.GetSubresource() != "eviction" {
|
||||
return false, nil, nil
|
||||
}
|
||||
createAct, ok := action.(core.CreateActionImpl)
|
||||
if !ok {
|
||||
return false, nil, fmt.Errorf("unable to convert action to core.CreateActionImpl")
|
||||
}
|
||||
eviction, ok := createAct.Object.(*policy.Eviction)
|
||||
if !ok || eviction.GetName() != "p1" {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
// Trigger addPod and wait for it to complete before returning
|
||||
// TooManyRequests, ensuring assumePod will find the entry already present.
|
||||
close(triggerAddPod)
|
||||
<-addPodDone
|
||||
|
||||
return true, nil, &apierrors.StatusError{
|
||||
ErrStatus: metav1.Status{
|
||||
Reason: metav1.StatusReasonTooManyRequests,
|
||||
Message: "Eviction triggered evacuation",
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
sharedInformerFactory.Start(ctx.Done())
|
||||
sharedInformerFactory.WaitForCacheSync(ctx.Done())
|
||||
|
||||
evictOpts := EvictOptions{StrategyName: "TestStrategy", ProfileName: "TestProfile"}
|
||||
podEvictor.EvictPod(ctx, p1, evictOpts)
|
||||
|
||||
// The "background" metric must have been emitted.
|
||||
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "background"}, 1)
|
||||
|
||||
// Simulate the background eviction completing: the pod is deleted.
|
||||
client.CoreV1().Pods(p1.Namespace).Delete(ctx, p1.Name, metav1.DeleteOptions{})
|
||||
|
||||
// Wait until DeleteFunc has fired and removed the pod from the cache.
|
||||
if err := wait.PollUntilContextTimeout(ctx, 100*time.Millisecond, wait.ForeverTestTimeout, true, func(ctx context.Context) (bool, error) {
|
||||
return !podEvictor.erCache.hasPod(p1), nil
|
||||
}); err != nil {
|
||||
t.Fatalf("Timed out waiting for background eviction to complete: %v", err)
|
||||
}
|
||||
|
||||
// "success" must also be 1: the background eviction completed.
|
||||
// Without the fix this will be 0 because assumePod returned early (entry
|
||||
// already present from addPod) leaving evictionAssumed=false.
|
||||
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "success"}, 1)
|
||||
}
|
||||
|
||||
// TestEvictionInBackgroundMetrics_PodCompleted verifies that when a pod
|
||||
// transitions to a terminal phase via UpdateFunc (without ever being deleted),
|
||||
// the correct metric is emitted: "success" for PodSucceeded, "error" for PodFailed.
|
||||
func TestEvictionInBackgroundMetrics_PodCompleted(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
phase v1.PodPhase
|
||||
expectedResult string
|
||||
}{
|
||||
{name: "PodSucceeded emits success", phase: v1.PodSucceeded, expectedResult: "success"},
|
||||
{name: "PodFailed emits error", phase: v1.PodFailed, expectedResult: "error"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), wait.ForeverTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
deschedulermetrics.Register()
|
||||
deschedulermetrics.PodsEvicted.Reset()
|
||||
deschedulermetrics.PodsEvictedTotal.Reset()
|
||||
|
||||
node1 := test.BuildTestNode("n1", 2000, 3000, 10, nil)
|
||||
ownerRef1 := test.GetReplicaSetOwnerRefList()
|
||||
p1 := test.BuildTestPod("p1", 100, 0, node1.Name, func(pod *v1.Pod) {
|
||||
pod.Namespace = "dev"
|
||||
pod.ObjectMeta.OwnerReferences = ownerRef1
|
||||
pod.Annotations = map[string]string{
|
||||
EvictionRequestAnnotationKey: "",
|
||||
}
|
||||
})
|
||||
|
||||
client := fakeclientset.NewSimpleClientset(node1, p1)
|
||||
sharedInformerFactory := informers.NewSharedInformerFactory(client, 0)
|
||||
_, eventRecorder := utils.GetRecorderAndBroadcaster(ctx, client)
|
||||
|
||||
podEvictor, err := NewPodEvictor(
|
||||
ctx,
|
||||
client,
|
||||
eventRecorder,
|
||||
sharedInformerFactory.Core().V1().Pods().Informer(),
|
||||
initFeatureGates(),
|
||||
NewOptions().WithMetricsEnabled(true),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error when creating a pod evictor: %v", err)
|
||||
}
|
||||
|
||||
client.PrependReactor("create", "pods", func(action core.Action) (bool, runtime.Object, error) {
|
||||
if action.GetSubresource() != "eviction" {
|
||||
return false, nil, nil
|
||||
}
|
||||
return true, nil, &apierrors.StatusError{
|
||||
ErrStatus: metav1.Status{
|
||||
Reason: metav1.StatusReasonTooManyRequests,
|
||||
Message: "Eviction triggered evacuation",
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
sharedInformerFactory.Start(ctx.Done())
|
||||
sharedInformerFactory.WaitForCacheSync(ctx.Done())
|
||||
|
||||
evictOpts := EvictOptions{StrategyName: "TestStrategy", ProfileName: "TestProfile"}
|
||||
podEvictor.EvictPod(ctx, p1, evictOpts)
|
||||
|
||||
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "background"}, 1)
|
||||
|
||||
// Transition the pod to a terminal phase without deleting it.
|
||||
p1Updated := p1.DeepCopy()
|
||||
p1Updated.Status.Phase = tc.phase
|
||||
client.CoreV1().Pods(p1.Namespace).UpdateStatus(ctx, p1Updated, metav1.UpdateOptions{})
|
||||
|
||||
// Wait for UpdateFunc to fire and remove the pod from the cache.
|
||||
if err := wait.PollUntilContextTimeout(ctx, 100*time.Millisecond, wait.ForeverTestTimeout, true, func(ctx context.Context) (bool, error) {
|
||||
return !podEvictor.erCache.hasPod(p1), nil
|
||||
}); err != nil {
|
||||
t.Fatalf("Timed out waiting for background eviction to complete: %v", err)
|
||||
}
|
||||
|
||||
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": tc.expectedResult}, 1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestEvictionInBackgroundMetrics_AssumedTimeout verifies that when an assumed
|
||||
// eviction entry expires in cleanCache, the "error" metric is emitted.
|
||||
func TestEvictionInBackgroundMetrics_AssumedTimeout(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), wait.ForeverTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
deschedulermetrics.Register()
|
||||
deschedulermetrics.PodsEvicted.Reset()
|
||||
deschedulermetrics.PodsEvictedTotal.Reset()
|
||||
|
||||
node1 := test.BuildTestNode("n1", 2000, 3000, 10, nil)
|
||||
ownerRef1 := test.GetReplicaSetOwnerRefList()
|
||||
p1 := test.BuildTestPod("p1", 100, 0, node1.Name, func(pod *v1.Pod) {
|
||||
pod.Namespace = "dev"
|
||||
pod.ObjectMeta.OwnerReferences = ownerRef1
|
||||
pod.Annotations = map[string]string{
|
||||
EvictionRequestAnnotationKey: "",
|
||||
}
|
||||
})
|
||||
|
||||
client := fakeclientset.NewSimpleClientset(node1, p1)
|
||||
sharedInformerFactory := informers.NewSharedInformerFactory(client, 0)
|
||||
_, eventRecorder := utils.GetRecorderAndBroadcaster(ctx, client)
|
||||
|
||||
podEvictor, err := NewPodEvictor(
|
||||
ctx,
|
||||
client,
|
||||
eventRecorder,
|
||||
sharedInformerFactory.Core().V1().Pods().Informer(),
|
||||
initFeatureGates(),
|
||||
NewOptions().WithMetricsEnabled(true),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error when creating a pod evictor: %v", err)
|
||||
}
|
||||
|
||||
client.PrependReactor("create", "pods", func(action core.Action) (bool, runtime.Object, error) {
|
||||
if action.GetSubresource() != "eviction" {
|
||||
return false, nil, nil
|
||||
}
|
||||
return true, nil, &apierrors.StatusError{
|
||||
ErrStatus: metav1.Status{
|
||||
Reason: metav1.StatusReasonTooManyRequests,
|
||||
Message: "Eviction triggered evacuation",
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
sharedInformerFactory.Start(ctx.Done())
|
||||
sharedInformerFactory.WaitForCacheSync(ctx.Done())
|
||||
|
||||
evictOpts := EvictOptions{StrategyName: "TestStrategy", ProfileName: "TestProfile"}
|
||||
podEvictor.EvictPod(ctx, p1, evictOpts)
|
||||
|
||||
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "background"}, 1)
|
||||
|
||||
// Back-date all assumed entries so they appear expired, then run cleanCache.
|
||||
podEvictor.erCache.mu.Lock()
|
||||
for uid, item := range podEvictor.erCache.requests {
|
||||
item.assumedTimestamp = metav1.NewTime(time.Now().Add(-time.Hour))
|
||||
podEvictor.erCache.requests[uid] = item
|
||||
}
|
||||
podEvictor.erCache.mu.Unlock()
|
||||
podEvictor.erCache.cleanCache(ctx)
|
||||
|
||||
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "error"}, 1)
|
||||
}
|
||||
|
||||
func assertEqualEvents(t *testing.T, expected []string, actual <-chan string) {
|
||||
t.Logf("Assert for events: %v", expected)
|
||||
c := time.After(wait.ForeverTestTimeout)
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
policy "k8s.io/api/policy/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
@@ -63,6 +64,26 @@ func setupTestSandbox(ctx context.Context, t *testing.T, initialObjects ...runti
|
||||
realFactory.Start(ctx.Done())
|
||||
realFactory.WaitForCacheSync(ctx.Done())
|
||||
|
||||
// Wait for initial objects to propagate from real client to fake client via event handlers.
|
||||
for _, obj := range initialObjects {
|
||||
if err := wait.PollUntilContextTimeout(ctx, time.Second, 5*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
exists, err := sandbox.hasRuntimeObjectInIndexer(obj)
|
||||
if err != nil {
|
||||
t.Logf("Error checking if object propagated to fake indexer: %v", err)
|
||||
return false, nil
|
||||
}
|
||||
if !exists {
|
||||
metaObj, _ := meta.Accessor(obj)
|
||||
t.Logf("Initial object %s/%s has not propagated to fake indexer yet, waiting...", metaObj.GetNamespace(), metaObj.GetName())
|
||||
}
|
||||
return exists, nil
|
||||
}); err != nil {
|
||||
metaObj, _ := meta.Accessor(obj)
|
||||
t.Fatalf("Initial object %s/%s did not propagate to fake indexer within timeout: %v",
|
||||
metaObj.GetNamespace(), metaObj.GetName(), err)
|
||||
}
|
||||
}
|
||||
|
||||
return sandbox, realFactory, realClient
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,8 @@ func (mc *MetricsCollector) NodeUsage(node *v1.Node) (api.ReferencedResourceList
|
||||
}
|
||||
|
||||
func (mc *MetricsCollector) HasSynced() bool {
|
||||
mc.mu.RLock()
|
||||
defer mc.mu.RUnlock()
|
||||
return mc.hasSynced
|
||||
}
|
||||
|
||||
|
||||
@@ -292,6 +292,28 @@ func SortPodsBasedOnAge(pods []*v1.Pod) {
|
||||
})
|
||||
}
|
||||
|
||||
// HasMatchingContainerWaitingState returns true if any container status has a
|
||||
// waiting reason present in the given set.
|
||||
func HasMatchingContainerWaitingState(statuses []v1.ContainerStatus, states sets.Set[string]) bool {
|
||||
for _, cs := range statuses {
|
||||
if cs.State.Waiting != nil && states.Has(cs.State.Waiting.Reason) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasMatchingContainerTerminatedState returns true if any container status has
|
||||
// a terminated reason present in the given set.
|
||||
func HasMatchingContainerTerminatedState(statuses []v1.ContainerStatus, states sets.Set[string]) bool {
|
||||
for _, cs := range statuses {
|
||||
if cs.State.Terminated != nil && states.Has(cs.State.Terminated.Reason) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GroupByNodeName(pods []*v1.Pod) map[string][]*v1.Pod {
|
||||
m := make(map[string][]*v1.Pod)
|
||||
for i := 0; i < len(pods); i++ {
|
||||
|
||||
@@ -260,10 +260,12 @@ func TestPromClientControllerSync_ClientCreation(t *testing.T) {
|
||||
ctrl := setupMode.setupFn(ctx, t, tc.objects)
|
||||
|
||||
// Set additional test-specific fields
|
||||
ctrl.mu.Lock()
|
||||
ctrl.currentPrometheusAuthToken = tc.currentAuthToken
|
||||
if tc.currentAuthToken != "" {
|
||||
ctrl.previousPrometheusClientTransport = &http.Transport{}
|
||||
}
|
||||
ctrl.mu.Unlock()
|
||||
|
||||
// Mock createPrometheusClient
|
||||
clientCreated := false
|
||||
@@ -302,12 +304,17 @@ func TestPromClientControllerSync_ClientCreation(t *testing.T) {
|
||||
}
|
||||
|
||||
// Verify token cleared expectations
|
||||
if tc.expectCurrentTokenCleared && ctrl.currentPrometheusAuthToken != "" {
|
||||
ctrl.mu.RLock()
|
||||
currentToken := ctrl.currentPrometheusAuthToken
|
||||
previousTransport := ctrl.previousPrometheusClientTransport
|
||||
ctrl.mu.RUnlock()
|
||||
|
||||
if tc.expectCurrentTokenCleared && currentToken != "" {
|
||||
t.Errorf("Expected current auth token to be cleared but it wasn't")
|
||||
}
|
||||
|
||||
// Verify previous transport cleared expectations
|
||||
if tc.expectPreviousTransportCleared && ctrl.previousPrometheusClientTransport != nil {
|
||||
if tc.expectPreviousTransportCleared && previousTransport != nil {
|
||||
t.Errorf("Expected previous transport to be cleared but it wasn't")
|
||||
}
|
||||
|
||||
@@ -320,8 +327,11 @@ func TestPromClientControllerSync_ClientCreation(t *testing.T) {
|
||||
if tc.expectClientCreated && len(tc.objects) > 0 {
|
||||
if secret, ok := tc.objects[0].(*v1.Secret); ok && secret.Data != nil {
|
||||
expectedToken := string(secret.Data[prometheusAuthTokenSecretKey])
|
||||
if ctrl.currentPrometheusAuthToken != expectedToken {
|
||||
t.Errorf("Expected current auth token to be %q but got %q", expectedToken, ctrl.currentPrometheusAuthToken)
|
||||
ctrl.mu.RLock()
|
||||
actualToken := ctrl.currentPrometheusAuthToken
|
||||
ctrl.mu.RUnlock()
|
||||
if actualToken != expectedToken {
|
||||
t.Errorf("Expected current auth token to be %q but got %q", expectedToken, actualToken)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -568,12 +578,17 @@ func TestPromClientControllerSync_EventHandler(t *testing.T) {
|
||||
t.Errorf("Expected %d clients created, but got %d", tc.expectedCreatedClientsCount, len(createdClients))
|
||||
}
|
||||
|
||||
if ctrl.currentPrometheusAuthToken != tc.expectedCurrentToken {
|
||||
t.Errorf("Expected current token to be %q, got %q", tc.expectedCurrentToken, ctrl.currentPrometheusAuthToken)
|
||||
ctrl.mu.RLock()
|
||||
actualToken := ctrl.currentPrometheusAuthToken
|
||||
actualTransport := ctrl.previousPrometheusClientTransport
|
||||
ctrl.mu.RUnlock()
|
||||
|
||||
if actualToken != tc.expectedCurrentToken {
|
||||
t.Errorf("Expected current token to be %q, got %q", tc.expectedCurrentToken, actualToken)
|
||||
}
|
||||
|
||||
if tc.expectedPreviousTransportCleared {
|
||||
if ctrl.previousPrometheusClientTransport != nil {
|
||||
if actualTransport != nil {
|
||||
t.Error("Expected previous transport to be cleared, but it was set")
|
||||
}
|
||||
}
|
||||
@@ -723,9 +738,10 @@ func TestReconcileInClusterSAToken(t *testing.T) {
|
||||
}
|
||||
_, descheduler, _, _ := initDescheduler(t, ctx, initFeatureGates(), deschedulerPolicy, nil, false)
|
||||
|
||||
// Override the fields needed for this test (no need for a mutex
|
||||
// since reconcileInClusterSAToken gets to run later)
|
||||
// Override the fields needed for this test
|
||||
descheduler.inClusterPromClientCtrl.mu.Lock()
|
||||
descheduler.inClusterPromClientCtrl.currentPrometheusAuthToken = tc.currentAuthToken
|
||||
descheduler.inClusterPromClientCtrl.mu.Unlock()
|
||||
descheduler.inClusterPromClientCtrl.inClusterConfig = tc.inClusterConfigFunc
|
||||
return descheduler.inClusterPromClientCtrl
|
||||
},
|
||||
@@ -735,12 +751,14 @@ func TestReconcileInClusterSAToken(t *testing.T) {
|
||||
ctrl := setupMode.init(t)
|
||||
|
||||
// Set previous transport and client if test expects them to be cleared
|
||||
ctrl.mu.Lock()
|
||||
if tc.expectPreviousTransportCleared {
|
||||
ctrl.previousPrometheusClientTransport = &http.Transport{}
|
||||
}
|
||||
if tc.expectPromClientCleared {
|
||||
ctrl.promClient = &mockPrometheusClient{name: "old-client"}
|
||||
}
|
||||
ctrl.mu.Unlock()
|
||||
|
||||
// Mock createPrometheusClient
|
||||
clientCreated := false
|
||||
@@ -779,20 +797,25 @@ func TestReconcileInClusterSAToken(t *testing.T) {
|
||||
}
|
||||
|
||||
// Verify token expectations
|
||||
if ctrl.currentPrometheusAuthToken != tc.expectCurrentToken {
|
||||
t.Errorf("Expected current token to be %q but got %q", tc.expectCurrentToken, ctrl.currentPrometheusAuthToken)
|
||||
ctrl.mu.RLock()
|
||||
actualToken := ctrl.currentPrometheusAuthToken
|
||||
actualTransport := ctrl.previousPrometheusClientTransport
|
||||
ctrl.mu.RUnlock()
|
||||
|
||||
if actualToken != tc.expectCurrentToken {
|
||||
t.Errorf("Expected current token to be %q but got %q", tc.expectCurrentToken, actualToken)
|
||||
}
|
||||
|
||||
// Verify previous transport cleared when expected
|
||||
if tc.expectPreviousTransportCleared {
|
||||
if tc.expectClientCreated {
|
||||
// Success case: new transport should be set
|
||||
if ctrl.previousPrometheusClientTransport == nil {
|
||||
if actualTransport == nil {
|
||||
t.Error("Expected previous transport to be set to new transport, but it was nil")
|
||||
}
|
||||
} else if tc.expectedErr != nil {
|
||||
// Failure case: transport should be nil
|
||||
if ctrl.previousPrometheusClientTransport != nil {
|
||||
if actualTransport != nil {
|
||||
t.Error("Expected previous transport to be cleared on error, but it was set")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,4 +46,4 @@ var defaultDeschedulerFeatureGates = map[featuregate.Feature]featuregate.Feature
|
||||
// Tests that need to modify feature gates for the duration of their test should use:
|
||||
//
|
||||
// defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.<FeatureName>, <value>)()
|
||||
var DefaultMutableFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate()
|
||||
var DefaultMutableFeatureGate featuregate.MutableVersionedFeatureGate = featuregate.NewFeatureGate()
|
||||
|
||||
@@ -90,7 +90,7 @@ func New(ctx context.Context, args runtime.Object, handle frameworktypes.Handle)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ev.args.NamespaceLabelSelector != nil && len(ev.args.NamespaceLabelSelector.MatchLabels) > 0 {
|
||||
if ev.args.NamespaceLabelSelector != nil && (len(ev.args.NamespaceLabelSelector.MatchLabels) > 0 || len(ev.args.NamespaceLabelSelector.MatchExpressions) > 0) {
|
||||
selector, nslErr := metav1.LabelSelectorAsSelector(ev.args.NamespaceLabelSelector)
|
||||
if nslErr != nil {
|
||||
return nil, fmt.Errorf("unable to convert namespaceLabelSelector to label selector: %w", nslErr)
|
||||
@@ -447,7 +447,7 @@ func (d *DefaultEvictor) PreEvictionFilter(pod *v1.Pod) bool {
|
||||
}
|
||||
}
|
||||
|
||||
if d.args.NamespaceLabelSelector == nil || len(d.args.NamespaceLabelSelector.MatchLabels) == 0 {
|
||||
if d.args.NamespaceLabelSelector == nil || (len(d.args.NamespaceLabelSelector.MatchLabels) == 0 && len(d.args.NamespaceLabelSelector.MatchExpressions) == 0) {
|
||||
return true
|
||||
}
|
||||
indexName := namespaceWithLabelSelector + d.handle.PluginInstanceID()
|
||||
|
||||
@@ -1030,13 +1030,13 @@ func initializePlugin(ctx context.Context, test testCase) (frameworktypes.Plugin
|
||||
objs = append(objs, ns)
|
||||
}
|
||||
|
||||
fakeClient := fake.NewSimpleClientset(objs...)
|
||||
fakeClient := fake.NewClientset(objs...)
|
||||
|
||||
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
|
||||
podInformer := sharedInformerFactory.Core().V1().Pods().Informer()
|
||||
_ = sharedInformerFactory.Policy().V1().PodDisruptionBudgets().Lister()
|
||||
_ = sharedInformerFactory.Core().V1().PersistentVolumeClaims().Lister()
|
||||
_ = sharedInformerFactory.Core().V1().Namespaces().Lister()
|
||||
_ = sharedInformerFactory.Core().V1().Namespaces().Informer()
|
||||
getPodsAssignedToNode, err := podutil.BuildGetPodsAssignedToNodeFunc(podInformer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build get pods assigned to node function error: %v", err)
|
||||
@@ -1283,9 +1283,7 @@ func TestMultipleProfilesWithDifferentNamespaceLabelSelectors(t *testing.T) {
|
||||
fakeClient := fake.NewClientset(node, nsProd, nsBackend, nsTest, podInProd, podInBackend, podInTest)
|
||||
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
|
||||
|
||||
_ = sharedInformerFactory.Core().V1().Namespaces().Lister()
|
||||
sharedInformerFactory.Start(ctx.Done())
|
||||
sharedInformerFactory.WaitForCacheSync(ctx.Done())
|
||||
_ = sharedInformerFactory.Core().V1().Namespaces().Informer()
|
||||
|
||||
// Create Profile 1: targets namespaces with env=prod
|
||||
profile1Args := &DefaultEvictorArgs{
|
||||
@@ -1328,6 +1326,9 @@ func TestMultipleProfilesWithDifferentNamespaceLabelSelectors(t *testing.T) {
|
||||
t.Fatalf("unable to initialize profile2 plugin: %v", err)
|
||||
}
|
||||
|
||||
sharedInformerFactory.Start(ctx.Done())
|
||||
sharedInformerFactory.WaitForCacheSync(ctx.Done())
|
||||
|
||||
// Test Profile 1: evicts pods in ns-prod, reject others
|
||||
t.Run("profile1", func(t *testing.T) {
|
||||
profile1 := profile1Plugin.(*DefaultEvictor)
|
||||
@@ -1362,3 +1363,145 @@ func TestMultipleProfilesWithDifferentNamespaceLabelSelectors(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNamespaceLabelSelectorAllOperations(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
node := test.BuildTestNode("node1", 1000, 2000, 10, nil)
|
||||
|
||||
nsProd := test.BuildTestNamespace("ns-prod")
|
||||
nsProd.Labels = map[string]string{"env": "prod", "tier": "frontend"}
|
||||
|
||||
nsStaging := test.BuildTestNamespace("ns-staging")
|
||||
nsStaging.Labels = map[string]string{"env": "staging", "tier": "backend"}
|
||||
|
||||
nsTest := test.BuildTestNamespace("ns-test")
|
||||
nsTest.Labels = map[string]string{"env": "test"}
|
||||
|
||||
nsNoLabels := test.BuildTestNamespace("ns-nolabels")
|
||||
nsNoLabels.Labels = map[string]string{}
|
||||
|
||||
podInProdFrontend := test.BuildTestPod("pod-in-prod", 100, 100, node.Name, func(pod *v1.Pod) {
|
||||
pod.Namespace = nsProd.Name
|
||||
test.SetNormalOwnerRef(pod)
|
||||
})
|
||||
|
||||
podInStagingBackend := test.BuildTestPod("pod-in-staging", 100, 100, node.Name, func(pod *v1.Pod) {
|
||||
pod.Namespace = nsStaging.Name
|
||||
test.SetNormalOwnerRef(pod)
|
||||
})
|
||||
|
||||
podInTest := test.BuildTestPod("pod-in-test", 100, 100, node.Name, func(pod *v1.Pod) {
|
||||
pod.Namespace = nsTest.Name
|
||||
test.SetNormalOwnerRef(pod)
|
||||
})
|
||||
|
||||
podInNoLabels := test.BuildTestPod("pod-in-nolabels", 100, 100, node.Name, func(pod *v1.Pod) {
|
||||
pod.Namespace = nsNoLabels.Name
|
||||
test.SetNormalOwnerRef(pod)
|
||||
})
|
||||
|
||||
fakeClient := fake.NewClientset(node, nsProd, nsStaging, nsTest, nsNoLabels, podInProdFrontend, podInStagingBackend, podInTest, podInNoLabels)
|
||||
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
|
||||
getPodAssignedToNode, _ := podutil.BuildGetPodsAssignedToNodeFunc(sharedInformerFactory.Core().V1().Pods().Informer())
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args *DefaultEvictorArgs
|
||||
pass []*v1.Pod
|
||||
fail []*v1.Pod
|
||||
}{
|
||||
{
|
||||
name: "MatchLabels and MatchExpressions In",
|
||||
args: &DefaultEvictorArgs{
|
||||
NamespaceLabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"tier": "frontend"},
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{Key: "env", Operator: metav1.LabelSelectorOpIn, Values: []string{"prod", "staging"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
pass: []*v1.Pod{podInProdFrontend},
|
||||
fail: []*v1.Pod{podInStagingBackend, podInTest},
|
||||
},
|
||||
{
|
||||
name: "MatchLabels and MatchExpressions Exists",
|
||||
args: &DefaultEvictorArgs{
|
||||
NamespaceLabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"tier": "backend"},
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{Key: "env", Operator: metav1.LabelSelectorOpExists},
|
||||
},
|
||||
},
|
||||
},
|
||||
pass: []*v1.Pod{podInStagingBackend},
|
||||
fail: []*v1.Pod{podInProdFrontend, podInTest},
|
||||
},
|
||||
{
|
||||
name: "Exists and In",
|
||||
args: &DefaultEvictorArgs{
|
||||
NamespaceLabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{Key: "tier", Operator: metav1.LabelSelectorOpExists},
|
||||
{Key: "env", Operator: metav1.LabelSelectorOpIn, Values: []string{"staging"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
pass: []*v1.Pod{podInStagingBackend},
|
||||
fail: []*v1.Pod{podInProdFrontend, podInTest},
|
||||
},
|
||||
{
|
||||
name: "DoesNotExist",
|
||||
args: &DefaultEvictorArgs{
|
||||
NamespaceLabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{Key: "tier", Operator: metav1.LabelSelectorOpDoesNotExist},
|
||||
},
|
||||
},
|
||||
},
|
||||
pass: []*v1.Pod{podInTest, podInNoLabels},
|
||||
fail: []*v1.Pod{podInProdFrontend, podInStagingBackend},
|
||||
},
|
||||
{
|
||||
name: "NotIn",
|
||||
args: &DefaultEvictorArgs{
|
||||
NamespaceLabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{Key: "env", Operator: metav1.LabelSelectorOpNotIn, Values: []string{"prod", "test"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
pass: []*v1.Pod{podInStagingBackend},
|
||||
fail: []*v1.Pod{podInProdFrontend, podInTest},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
plugin, err := New(ctx, tc.args, &frameworkfake.HandleImpl{
|
||||
ClientsetImpl: fakeClient,
|
||||
GetPodsAssignedToNodeFuncImpl: getPodAssignedToNode,
|
||||
SharedInformerFactoryImpl: sharedInformerFactory,
|
||||
PluginInstanceIDImpl: "test-" + tc.name,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to initialize plugin: %v", err)
|
||||
}
|
||||
|
||||
sharedInformerFactory.Start(ctx.Done())
|
||||
sharedInformerFactory.WaitForCacheSync(ctx.Done())
|
||||
|
||||
evictor := plugin.(*DefaultEvictor)
|
||||
for _, pod := range tc.pass {
|
||||
if result := evictor.PreEvictionFilter(pod); !result {
|
||||
t.Errorf("pod %s expected to pass (true), got false", pod.Name)
|
||||
}
|
||||
}
|
||||
for _, pod := range tc.fail {
|
||||
if result := evictor.PreEvictionFilter(pod); result {
|
||||
t.Errorf("pod %s expected to fail (false), got true", pod.Name)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,18 +103,13 @@ func NewLowNodeUtilization(
|
||||
}
|
||||
|
||||
// this plugins supports different ways of collecting usage data. each
|
||||
// different way provides its own "usageClient". here we make sure we
|
||||
// have the correct one or an error is triggered. XXX MetricsServer is
|
||||
// deprecated, removed once dropped.
|
||||
// different way provides its own "usageClient". the metrics-based client
|
||||
// is reset at every extension point so we always get the latest prometheus
|
||||
// client (whose SA token can be rotated after plugin creation).
|
||||
// XXX MetricsServer is deprecated, removed once dropped.
|
||||
var usageClient usageClient = newRequestedUsageClient(
|
||||
extendedResourceNames, handle.GetPodsAssignedToNodeFunc(),
|
||||
)
|
||||
if metrics != nil {
|
||||
usageClient, err = usageClientForMetrics(args, handle, extendedResourceNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &LowNodeUtilization{
|
||||
logger: logger,
|
||||
@@ -134,12 +129,33 @@ func (l *LowNodeUtilization) Name() string {
|
||||
return LowNodeUtilizationPluginName
|
||||
}
|
||||
|
||||
// resetUsageClient refreshes the usageClient field from the current handle
|
||||
// state. It must be called at the start of every extension point so that a
|
||||
// rotated prometheus SA token is picked up without restarting the process.
|
||||
func (l *LowNodeUtilization) resetUsageClient() error {
|
||||
if l.args.MetricsUtilization == nil {
|
||||
return nil
|
||||
}
|
||||
client, err := usageClientForMetrics(l.args, l.handle, l.extendedResourceNames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.usageClient = client
|
||||
return nil
|
||||
}
|
||||
|
||||
// Balance holds the main logic of the plugin. It evicts pods from over
|
||||
// utilized nodes to under utilized nodes. The goal here is to evenly
|
||||
// distribute pods across nodes.
|
||||
func (l *LowNodeUtilization) Balance(ctx context.Context, nodes []*v1.Node) *frameworktypes.Status {
|
||||
logger := klog.FromContext(klog.NewContext(ctx, l.logger)).WithValues("ExtensionPoint", frameworktypes.BalanceExtensionPoint)
|
||||
|
||||
if err := l.resetUsageClient(); err != nil {
|
||||
return &frameworktypes.Status{
|
||||
Err: fmt.Errorf("error initializing usage client: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
if err := l.usageClient.sync(ctx, nodes); err != nil {
|
||||
return &frameworktypes.Status{
|
||||
Err: fmt.Errorf("error getting node usage: %v", err),
|
||||
|
||||
@@ -1661,3 +1661,83 @@ func TestLowNodeUtilizationWithPrometheusMetrics(t *testing.T) {
|
||||
t.Run(tc.name, testFnc(false, tc.expectedPodsEvicted))
|
||||
}
|
||||
}
|
||||
|
||||
// TestLowNodeUtilizationPrometheusClientNilAtCreation ensures the plugin
|
||||
// tolerates a nil prometheus client at creation time (e.g. in-cluster SA token
|
||||
// not yet reconciled) and correctly picks up the client exposed by the handle
|
||||
// at Balance call time.
|
||||
func TestLowNodeUtilizationPrometheusClientNilAtCreation(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
n1NodeName := "n1"
|
||||
n2NodeName := "n2"
|
||||
n3NodeName := "n3"
|
||||
|
||||
nodes := []*v1.Node{
|
||||
test.BuildTestNode(n1NodeName, 4000, 3000, 9, nil),
|
||||
test.BuildTestNode(n2NodeName, 4000, 3000, 10, nil),
|
||||
test.BuildTestNode(n3NodeName, 4000, 3000, 10, nil),
|
||||
}
|
||||
pods := []*v1.Pod{
|
||||
test.BuildTestPod("p1", 400, 0, n1NodeName, test.SetRSOwnerRef),
|
||||
test.BuildTestPod("p2", 400, 0, n1NodeName, test.SetRSOwnerRef),
|
||||
test.BuildTestPod("p3", 400, 0, n1NodeName, test.SetRSOwnerRef),
|
||||
test.BuildTestPod("p4", 400, 0, n1NodeName, test.SetRSOwnerRef),
|
||||
test.BuildTestPod("p5", 400, 0, n1NodeName, test.SetRSOwnerRef),
|
||||
test.BuildTestPod("p6", 400, 0, n2NodeName, test.SetRSOwnerRef),
|
||||
}
|
||||
|
||||
var objs []runtime.Object
|
||||
for _, node := range nodes {
|
||||
objs = append(objs, node)
|
||||
}
|
||||
for _, pod := range pods {
|
||||
objs = append(objs, pod)
|
||||
}
|
||||
fakeClient := fake.NewSimpleClientset(objs...)
|
||||
|
||||
handle, podEvictor, err := frameworktesting.InitFrameworkHandle(
|
||||
ctx, fakeClient, nil, defaultevictor.DefaultEvictorArgs{NodeFit: true}, nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to initialize a framework handle: %v", err)
|
||||
}
|
||||
|
||||
// PrometheusClientImpl is intentionally nil here — the in-cluster SA token
|
||||
// has not been reconciled yet at plugin-creation time.
|
||||
args := &LowNodeUtilizationArgs{
|
||||
Thresholds: api.ResourceThresholds{MetricResource: 30},
|
||||
TargetThresholds: api.ResourceThresholds{MetricResource: 50},
|
||||
MetricsUtilization: &MetricsUtilization{
|
||||
Source: api.PrometheusMetrics,
|
||||
Prometheus: &Prometheus{
|
||||
Query: "instance:node_cpu:rate:sum",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
plugin, err := NewLowNodeUtilization(ctx, args, handle)
|
||||
if err != nil {
|
||||
t.Fatalf("plugin creation must succeed even when prometheus client is nil: %v", err)
|
||||
}
|
||||
|
||||
// Simulate the SA token becoming available before the first descheduling cycle.
|
||||
handle.PrometheusClientImpl = &fakePromClient{
|
||||
result: model.Vector{
|
||||
sample("instance:node_cpu:rate:sum", n1NodeName, 0.57), // over target
|
||||
sample("instance:node_cpu:rate:sum", n2NodeName, 0.42), // over target
|
||||
sample("instance:node_cpu:rate:sum", n3NodeName, 0.20), // under threshold
|
||||
},
|
||||
dataType: model.ValVector,
|
||||
}
|
||||
|
||||
status := plugin.(frameworktypes.BalancePlugin).Balance(ctx, nodes)
|
||||
if status != nil {
|
||||
t.Fatalf("Balance failed: %v", status.Err)
|
||||
}
|
||||
|
||||
if podEvictor.TotalEvicted() == 0 {
|
||||
t.Error("expected at least one pod to be evicted")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ type notSupportedError struct {
|
||||
}
|
||||
|
||||
func (e notSupportedError) Error() string {
|
||||
return "maximum number of evicted pods per node reached"
|
||||
return "pod usage quantification is not supported by this usage client"
|
||||
}
|
||||
|
||||
func newNotSupportedError(usageClientType UsageClientType) *notSupportedError {
|
||||
|
||||
@@ -2,136 +2,116 @@
|
||||
|
||||
## What It Does
|
||||
|
||||
The PodLifeTime plugin evicts pods that have been running for too long. You can configure a maximum age threshold, and the plugin evicts pods older than that threshold. The oldest pods are evicted first.
|
||||
The PodLifeTime plugin evicts pods based on their age, status phase, condition transitions, container states, exit codes, and owner kinds. It can be used for simple age-based eviction or for fine-grained cleanup of pods matching specific transition criteria.
|
||||
|
||||
## How It Works
|
||||
|
||||
The plugin examines all pods across your nodes and selects those that exceed the configured age threshold. You can further narrow down which pods are considered by specifying:
|
||||
The plugin builds a filter chain from the configured criteria. All non-empty filter categories are ANDed together (a pod must satisfy every specified filter to be evicted). Within each filter category, items are ORed (matching any one entry satisfies that filter).
|
||||
|
||||
- Which namespaces to include or exclude
|
||||
- Which labels pods must have
|
||||
- Which states pods must be in (e.g., Running, Pending, CrashLoopBackOff)
|
||||
|
||||
Once pods are selected, they are sorted by age (oldest first) and evicted in that order. Eviction stops when limits are reached (per-node limits, total limits, or Pod Disruption Budget constraints).
|
||||
Once pods are selected, they are sorted by their creation time with the oldest first, then evicted in order. Eviction stops when limits are reached (per-node limits, total limits, or Pod Disruption Budget constraints).
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **Resource Leakage Mitigation**: Restart long-running pods that may have accumulated memory leaks, stale cache, or resource leaks
|
||||
- **Evict completed/succeeded pods that have been idle too long**:
|
||||
```yaml
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 604800 # 7 days
|
||||
states: [Running]
|
||||
states: [Succeeded]
|
||||
conditions:
|
||||
- reason: PodCompleted
|
||||
status: "True"
|
||||
minTimeSinceLastTransitionSeconds: 14400 # 4 hours
|
||||
```
|
||||
|
||||
- **Ephemeral Workload Cleanup**: Remove long-running batch jobs, test pods, or temporary workloads that have exceeded their expected lifetime
|
||||
- **Evict failed pods**, excluding Job-owned pods:
|
||||
```yaml
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 7200 # 2 hours
|
||||
states: [Succeeded, Failed]
|
||||
```
|
||||
|
||||
- **Node Hygiene**: Remove forgotten or stuck pods that are consuming resources but not making progress
|
||||
```yaml
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 3600 # 1 hour
|
||||
states: [CrashLoopBackOff, ImagePullBackOff, ErrImagePull]
|
||||
states: [Failed]
|
||||
exitCodes: [1]
|
||||
ownerKinds:
|
||||
exclude: [Job]
|
||||
maxPodLifeTimeSeconds: 3600
|
||||
includingInitContainers: true
|
||||
```
|
||||
|
||||
- **Config/Secret Update Pickup**: Force pod restart to pick up updated ConfigMaps, Secrets, or environment variables
|
||||
```yaml
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 86400 # 1 day
|
||||
states: [Running]
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
config-refresh: enabled
|
||||
```
|
||||
|
||||
- **Security Rotation**: Periodically refresh pods to pick up new security tokens, certificates, or patched container images
|
||||
```yaml
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 259200 # 3 days
|
||||
states: [Running]
|
||||
namespaces:
|
||||
exclude: [kube-system]
|
||||
```
|
||||
|
||||
- **Dev/Test Environment Cleanup**: Automatically clean up old pods in development or staging namespaces
|
||||
```yaml
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 86400 # 1 day
|
||||
namespaces:
|
||||
include: [dev, staging, test]
|
||||
```
|
||||
|
||||
- **Cluster Health Freshness**: Ensure pods periodically restart to maintain cluster health and verify workloads can recover from restarts
|
||||
- **Resource Leakage Mitigation**: Restart long-running pods that may have accumulated memory leaks:
|
||||
```yaml
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 604800 # 7 days
|
||||
states: [Running]
|
||||
namespaces:
|
||||
exclude: [kube-system, production]
|
||||
```
|
||||
|
||||
- **Rebalancing Assistance**: Work alongside other descheduler strategies by removing old pods to allow better pod distribution
|
||||
- **Clean up stuck pods in CrashLoopBackOff**:
|
||||
```yaml
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 1209600 # 14 days
|
||||
states: [Running]
|
||||
states: [CrashLoopBackOff, ImagePullBackOff]
|
||||
```
|
||||
|
||||
- **Non-Critical Stateful Refresh**: Occasionally reset tolerable stateful workloads that can handle data loss or have external backup mechanisms
|
||||
- **Evict pods owned only by specific kinds**:
|
||||
```yaml
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 2592000 # 30 days
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
stateful-tier: cache
|
||||
states: [Succeeded, Failed]
|
||||
ownerKinds:
|
||||
include: [Job]
|
||||
maxPodLifeTimeSeconds: 600
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
| Parameter | Description | Type | Required | Default |
|
||||
|-----------|-------------|------|----------|---------|
|
||||
| `maxPodLifeTimeSeconds` | Pods older than this many seconds are evicted | `uint` | Yes | - |
|
||||
| `namespaces` | Limit eviction to specific namespaces (or exclude specific namespaces) | `Namespaces` | No | `nil` |
|
||||
| `maxPodLifeTimeSeconds` | Pods older than this many seconds are evicted | `uint` | No* | `nil` |
|
||||
| `states` | Filter pods by phase, pod status reason, or container waiting/terminated reason. A pod matches if any of its state values appear in this list | `[]string` | No | `nil` |
|
||||
| `conditions` | Only evict pods with matching status conditions (see PodConditionFilter) | `[]PodConditionFilter` | No | `nil` |
|
||||
| `exitCodes` | Only evict pods with matching container terminated exit codes | `[]int32` | No | `nil` |
|
||||
| `ownerKinds` | Include or exclude pods by owner reference kind | `OwnerKinds` | No | `nil` |
|
||||
| `namespaces` | Limit eviction to specific namespaces (include or exclude) | `Namespaces` | No | `nil` |
|
||||
| `labelSelector` | Only evict pods matching these labels | `metav1.LabelSelector` | No | `nil` |
|
||||
| `states` | Only evict pods in specific states (e.g., Running, CrashLoopBackOff) | `[]string` | No | `nil` |
|
||||
| `includingInitContainers` | When checking states, also check init container states | `bool` | No | `false` |
|
||||
| `includingEphemeralContainers` | When checking states, also check ephemeral container states | `bool` | No | `false` |
|
||||
| `includingInitContainers` | Extend state/exitCode filtering to init containers | `bool` | No | `false` |
|
||||
| `includingEphemeralContainers` | Extend state filtering to ephemeral containers | `bool` | No | `false` |
|
||||
|
||||
### Discovering states
|
||||
*At least one filtering criterion must be specified (`maxPodLifeTimeSeconds`, `states`, `conditions`, or `exitCodes`).
|
||||
|
||||
Each pod is checked for the following locations to discover its relevant state:
|
||||
### States
|
||||
|
||||
1. **Pod Phase** - The overall pod lifecycle phase:
|
||||
- `Running` - Pod is running on a node
|
||||
- `Pending` - Pod has been accepted but containers are not yet running
|
||||
- `Succeeded` - All containers terminated successfully
|
||||
- `Failed` - All containers terminated, at least one failed
|
||||
- `Unknown` - Pod state cannot be determined
|
||||
The `states` field matches pods using an OR across these categories:
|
||||
|
||||
2. **Pod Status Reason** - Why the pod is in its current state:
|
||||
- `NodeAffinity` - Pod cannot be scheduled due to node affinity rules
|
||||
- `NodeLost` - Node hosting the pod is lost
|
||||
- `Shutdown` - Pod terminated due to node shutdown
|
||||
- `UnexpectedAdmissionError` - Pod admission failed unexpectedly
|
||||
| Category | Examples |
|
||||
|----------|----------|
|
||||
| Pod phase | `Running`, `Pending`, `Succeeded`, `Failed`, `Unknown` |
|
||||
| Pod status reason | `NodeAffinity`, `NodeLost`, `Shutdown`, `UnexpectedAdmissionError` |
|
||||
| Container waiting reason | `CrashLoopBackOff`, `ImagePullBackOff`, `ErrImagePull`, `CreateContainerConfigError`, `CreateContainerError`, `InvalidImageName`, `PodInitializing`, `ContainerCreating` |
|
||||
| Container terminated reason | `OOMKilled`, `Error`, `Completed`, `DeadlineExceeded`, `Evicted`, `ContainerCannotRun`, `StartError` |
|
||||
|
||||
3. **Container Waiting Reason** - Why containers are waiting to start:
|
||||
- `PodInitializing` - Pod is still initializing
|
||||
- `ContainerCreating` - Container is being created
|
||||
- `ImagePullBackOff` - Image pull is failing and backing off
|
||||
- `CrashLoopBackOff` - Container is crashing repeatedly
|
||||
- `CreateContainerConfigError` - Container configuration is invalid
|
||||
- `ErrImagePull` - Image cannot be pulled
|
||||
- `CreateContainerError` - Container creation failed
|
||||
- `InvalidImageName` - Image name is invalid
|
||||
When `includingInitContainers` is true, init container states are also checked. When `includingEphemeralContainers` is true, ephemeral container states are also checked.
|
||||
|
||||
By default, only regular containers are checked. Enable `includingInitContainers` or `includingEphemeralContainers` to also check those container types.
|
||||
### PodConditionFilter
|
||||
|
||||
Each condition filter matches against `pod.status.conditions[]` entries. Within a single filter, all specified field-level checks must match (AND). Unset fields are not checked. Across the list, condition filters are ORed — a pod is eligible for eviction if **any** of the listed condition filters match.
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `type` | Condition type (e.g., `Ready`, `Initialized`, `ContainersReady`) |
|
||||
| `status` | Condition status (`True`, `False`, `Unknown`) |
|
||||
| `reason` | Condition reason (e.g., `PodCompleted`) |
|
||||
| `minTimeSinceLastTransitionSeconds` | Require the matching condition's `lastTransitionTime` to be at least this many seconds in the past |
|
||||
|
||||
At least one of these fields must be set per filter entry.
|
||||
|
||||
When `minTimeSinceLastTransitionSeconds` is set on a filter, a pod's condition must both match the type/status/reason fields AND have transitioned long enough ago. If the condition has no `lastTransitionTime`, it does not match.
|
||||
|
||||
### OwnerKinds
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `include` | Only evict pods owned by these kinds |
|
||||
| `exclude` | Do not evict pods owned by these kinds |
|
||||
|
||||
At most one of `include`/`exclude` may be set.
|
||||
|
||||
## Example
|
||||
|
||||
### Age-based eviction with state filter
|
||||
|
||||
```yaml
|
||||
apiVersion: descheduler/v1alpha2
|
||||
kind: DeschedulerPolicy
|
||||
@@ -145,11 +125,36 @@ profiles:
|
||||
- name: PodLifeTime
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 86400 # 1 day
|
||||
states:
|
||||
- Running
|
||||
namespaces:
|
||||
include:
|
||||
- default
|
||||
states:
|
||||
- Running
|
||||
```
|
||||
|
||||
This configuration evicts Running pods in the `default` namespace that are older than 1 day.
|
||||
### Transition-based eviction for completed pods
|
||||
|
||||
```yaml
|
||||
apiVersion: descheduler/v1alpha2
|
||||
kind: DeschedulerPolicy
|
||||
profiles:
|
||||
- name: default
|
||||
plugins:
|
||||
deschedule:
|
||||
enabled:
|
||||
- name: PodLifeTime
|
||||
pluginConfig:
|
||||
- name: PodLifeTime
|
||||
args:
|
||||
states:
|
||||
- Succeeded
|
||||
conditions:
|
||||
- reason: PodCompleted
|
||||
status: "True"
|
||||
minTimeSinceLastTransitionSeconds: 14400
|
||||
namespaces:
|
||||
include:
|
||||
- default
|
||||
```
|
||||
|
||||
This configuration evicts Succeeded pods in the `default` namespace that have a `PodCompleted` condition with status `True` and whose last matching transition happened more than 4 hours ago.
|
||||
|
||||
@@ -26,17 +26,16 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
frameworktypes "sigs.k8s.io/descheduler/pkg/framework/types"
|
||||
|
||||
"sigs.k8s.io/descheduler/pkg/descheduler/evictions"
|
||||
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
|
||||
frameworktypes "sigs.k8s.io/descheduler/pkg/framework/types"
|
||||
)
|
||||
|
||||
const PluginName = "PodLifeTime"
|
||||
|
||||
var _ frameworktypes.DeschedulePlugin = &PodLifeTime{}
|
||||
|
||||
// PodLifeTime evicts pods on the node that violate the max pod lifetime threshold
|
||||
// PodLifeTime evicts pods matching configurable lifetime and status transition criteria.
|
||||
type PodLifeTime struct {
|
||||
logger klog.Logger
|
||||
handle frameworktypes.Handle
|
||||
@@ -44,12 +43,13 @@ type PodLifeTime struct {
|
||||
podFilter podutil.FilterFunc
|
||||
}
|
||||
|
||||
// New builds plugin from its arguments while passing a handle
|
||||
// New builds plugin from its arguments while passing a handle.
|
||||
func New(ctx context.Context, args runtime.Object, handle frameworktypes.Handle) (frameworktypes.Plugin, error) {
|
||||
podLifeTimeArgs, ok := args.(*PodLifeTimeArgs)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("want args to be of type PodLifeTimeArgs, got %T", args)
|
||||
}
|
||||
|
||||
logger := klog.FromContext(ctx).WithValues("plugin", PluginName)
|
||||
|
||||
var includedNamespaces, excludedNamespaces sets.Set[string]
|
||||
@@ -69,53 +69,80 @@ func New(ctx context.Context, args runtime.Object, handle frameworktypes.Handle)
|
||||
return nil, fmt.Errorf("error initializing pod filter function: %v", err)
|
||||
}
|
||||
|
||||
podFilter = podutil.WrapFilterFuncs(podFilter, func(pod *v1.Pod) bool {
|
||||
podAgeSeconds := int(metav1.Now().Sub(pod.GetCreationTimestamp().Local()).Seconds())
|
||||
return podAgeSeconds > int(*podLifeTimeArgs.MaxPodLifeTimeSeconds)
|
||||
})
|
||||
if podLifeTimeArgs.MaxPodLifeTimeSeconds != nil {
|
||||
podFilter = podutil.WrapFilterFuncs(podFilter, func(pod *v1.Pod) bool {
|
||||
podAge := metav1.Now().Sub(pod.GetCreationTimestamp().Local())
|
||||
if podAge < 0 {
|
||||
return false
|
||||
}
|
||||
return uint(podAge.Seconds()) > *podLifeTimeArgs.MaxPodLifeTimeSeconds
|
||||
})
|
||||
}
|
||||
|
||||
if len(podLifeTimeArgs.States) > 0 {
|
||||
states := sets.New(podLifeTimeArgs.States...)
|
||||
includeInit := podLifeTimeArgs.IncludingInitContainers
|
||||
includeEphemeral := podLifeTimeArgs.IncludingEphemeralContainers
|
||||
podFilter = podutil.WrapFilterFuncs(podFilter, func(pod *v1.Pod) bool {
|
||||
// Pod Status Phase
|
||||
if states.Has(string(pod.Status.Phase)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Pod Status Reason
|
||||
if states.Has(pod.Status.Reason) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Init Container Status Reason
|
||||
if podLifeTimeArgs.IncludingInitContainers {
|
||||
for _, containerStatus := range pod.Status.InitContainerStatuses {
|
||||
if containerStatus.State.Waiting != nil && states.Has(containerStatus.State.Waiting.Reason) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if podutil.HasMatchingContainerWaitingState(pod.Status.ContainerStatuses, states) ||
|
||||
podutil.HasMatchingContainerTerminatedState(pod.Status.ContainerStatuses, states) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Ephemeral Container Status Reason
|
||||
if podLifeTimeArgs.IncludingEphemeralContainers {
|
||||
for _, containerStatus := range pod.Status.EphemeralContainerStatuses {
|
||||
if containerStatus.State.Waiting != nil && states.Has(containerStatus.State.Waiting.Reason) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if includeInit && (podutil.HasMatchingContainerWaitingState(pod.Status.InitContainerStatuses, states) ||
|
||||
podutil.HasMatchingContainerTerminatedState(pod.Status.InitContainerStatuses, states)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Container Status Reason
|
||||
for _, containerStatus := range pod.Status.ContainerStatuses {
|
||||
if containerStatus.State.Waiting != nil && states.Has(containerStatus.State.Waiting.Reason) {
|
||||
return true
|
||||
}
|
||||
if includeEphemeral && (podutil.HasMatchingContainerWaitingState(pod.Status.EphemeralContainerStatuses, states) ||
|
||||
podutil.HasMatchingContainerTerminatedState(pod.Status.EphemeralContainerStatuses, states)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
if podLifeTimeArgs.OwnerKinds != nil {
|
||||
if len(podLifeTimeArgs.OwnerKinds.Include) > 0 {
|
||||
includeKinds := sets.New(podLifeTimeArgs.OwnerKinds.Include...)
|
||||
podFilter = podutil.WrapFilterFuncs(podFilter, func(pod *v1.Pod) bool {
|
||||
for _, owner := range podutil.OwnerRef(pod) {
|
||||
if includeKinds.Has(owner.Kind) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
} else if len(podLifeTimeArgs.OwnerKinds.Exclude) > 0 {
|
||||
excludeKinds := sets.New(podLifeTimeArgs.OwnerKinds.Exclude...)
|
||||
podFilter = podutil.WrapFilterFuncs(podFilter, func(pod *v1.Pod) bool {
|
||||
for _, owner := range podutil.OwnerRef(pod) {
|
||||
if excludeKinds.Has(owner.Kind) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(podLifeTimeArgs.Conditions) > 0 {
|
||||
podFilter = podutil.WrapFilterFuncs(podFilter, func(pod *v1.Pod) bool {
|
||||
return matchesAnyPodConditionFilter(pod, podLifeTimeArgs.Conditions)
|
||||
})
|
||||
}
|
||||
|
||||
if len(podLifeTimeArgs.ExitCodes) > 0 {
|
||||
exitCodesSet := sets.New(podLifeTimeArgs.ExitCodes...)
|
||||
podFilter = podutil.WrapFilterFuncs(podFilter, func(pod *v1.Pod) bool {
|
||||
return matchesAnyExitCode(pod, exitCodesSet, podLifeTimeArgs.IncludingInitContainers)
|
||||
})
|
||||
}
|
||||
|
||||
return &PodLifeTime{
|
||||
logger: logger,
|
||||
handle: handle,
|
||||
@@ -132,7 +159,6 @@ func (d *PodLifeTime) Name() string {
|
||||
// Deschedule extension point implementation for the plugin
|
||||
func (d *PodLifeTime) Deschedule(ctx context.Context, nodes []*v1.Node) *frameworktypes.Status {
|
||||
podsToEvict := make([]*v1.Pod, 0)
|
||||
nodeMap := make(map[string]*v1.Node, len(nodes))
|
||||
logger := klog.FromContext(klog.NewContext(ctx, d.logger)).WithValues("ExtensionPoint", frameworktypes.DescheduleExtensionPoint)
|
||||
for _, node := range nodes {
|
||||
logger.V(2).Info("Processing node", "node", klog.KObj(node))
|
||||
@@ -143,8 +169,6 @@ func (d *PodLifeTime) Deschedule(ctx context.Context, nodes []*v1.Node) *framewo
|
||||
Err: fmt.Errorf("error listing pods on a node: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
nodeMap[node.Name] = node
|
||||
podsToEvict = append(podsToEvict, pods...)
|
||||
}
|
||||
|
||||
@@ -170,3 +194,61 @@ loop:
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// matchesAnyPodConditionFilter returns true if the pod has at least one
|
||||
// condition satisfying any of the given filters (OR across filters).
|
||||
func matchesAnyPodConditionFilter(pod *v1.Pod, filters []PodConditionFilter) bool {
|
||||
for _, f := range filters {
|
||||
for _, cond := range pod.Status.Conditions {
|
||||
if !matchesConditionFields(cond, f) {
|
||||
continue
|
||||
}
|
||||
if f.MinTimeSinceLastTransitionSeconds != nil {
|
||||
if cond.LastTransitionTime.IsZero() {
|
||||
continue
|
||||
}
|
||||
idle := metav1.Now().Sub(cond.LastTransitionTime.Time)
|
||||
if idle < 0 || uint(idle.Seconds()) < *f.MinTimeSinceLastTransitionSeconds {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// matchesConditionFields checks type, status, and reason fields of a single
|
||||
// condition against a filter. Unset filter fields are not checked.
|
||||
func matchesConditionFields(cond v1.PodCondition, filter PodConditionFilter) bool {
|
||||
if filter.Type != "" && string(cond.Type) != filter.Type {
|
||||
return false
|
||||
}
|
||||
if filter.Status != "" && string(cond.Status) != filter.Status {
|
||||
return false
|
||||
}
|
||||
if filter.Reason != "" && cond.Reason != filter.Reason {
|
||||
return false
|
||||
}
|
||||
// validation ensures that at least one of type, status, reason, or minTimeSinceLastTransitionSeconds is set
|
||||
return true
|
||||
}
|
||||
|
||||
func matchesAnyExitCode(pod *v1.Pod, exitCodes sets.Set[int32], includeInit bool) bool {
|
||||
if hasMatchingExitCode(pod.Status.ContainerStatuses, exitCodes) {
|
||||
return true
|
||||
}
|
||||
if includeInit && hasMatchingExitCode(pod.Status.InitContainerStatuses, exitCodes) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasMatchingExitCode(statuses []v1.ContainerStatus, exitCodes sets.Set[int32]) bool {
|
||||
for _, cs := range statuses {
|
||||
if cs.State.Terminated != nil && exitCodes.Has(cs.State.Terminated.ExitCode) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
utilptr "k8s.io/utils/ptr"
|
||||
|
||||
"sigs.k8s.io/descheduler/pkg/api"
|
||||
"sigs.k8s.io/descheduler/pkg/descheduler/evictions"
|
||||
"sigs.k8s.io/descheduler/pkg/framework/plugins/defaultevictor"
|
||||
frameworktesting "sigs.k8s.io/descheduler/pkg/framework/testing"
|
||||
@@ -40,6 +41,8 @@ const nodeName1 = "n1"
|
||||
var (
|
||||
olderPodCreationTime = metav1.NewTime(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC))
|
||||
newerPodCreationTime = metav1.NewTime(time.Now())
|
||||
oldTransitionTime = metav1.NewTime(time.Now().Add(-2 * time.Hour))
|
||||
newTransitionTime = metav1.NewTime(time.Now().Add(-1 * time.Minute))
|
||||
)
|
||||
|
||||
func buildTestNode1() *v1.Node {
|
||||
@@ -73,20 +76,32 @@ func buildTestPodWithRSOwnerRefWithPendingPhaseForNode1(name string, creationTim
|
||||
})
|
||||
}
|
||||
|
||||
func buildPod(name, nodeName string, apply func(*v1.Pod)) *v1.Pod {
|
||||
pod := test.BuildTestPod(name, 1, 1, nodeName, func(p *v1.Pod) {
|
||||
p.ObjectMeta.OwnerReferences = test.GetReplicaSetOwnerRefList()
|
||||
if apply != nil {
|
||||
apply(p)
|
||||
}
|
||||
})
|
||||
return pod
|
||||
}
|
||||
|
||||
type podLifeTimeTestCase struct {
|
||||
description string
|
||||
args *PodLifeTimeArgs
|
||||
pods []*v1.Pod
|
||||
nodes []*v1.Node
|
||||
expectedEvictedPods []string // if specified, will assert specific pods were evicted
|
||||
expectedEvictedPods []string
|
||||
expectedEvictedPodCount uint
|
||||
ignorePvcPods bool
|
||||
nodeFit bool
|
||||
maxPodsToEvictPerNode *uint
|
||||
maxPodsToEvictPerNamespace *uint
|
||||
maxPodsToEvictTotal *uint
|
||||
}
|
||||
|
||||
func runPodLifeTimeTest(t *testing.T, tc podLifeTimeTestCase) {
|
||||
t.Helper()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
@@ -106,7 +121,7 @@ func runPodLifeTimeTest(t *testing.T, tc podLifeTimeTestCase) {
|
||||
WithMaxPodsToEvictPerNode(tc.maxPodsToEvictPerNode).
|
||||
WithMaxPodsToEvictPerNamespace(tc.maxPodsToEvictPerNamespace).
|
||||
WithMaxPodsToEvictTotal(tc.maxPodsToEvictTotal),
|
||||
defaultevictor.DefaultEvictorArgs{IgnorePvcPods: tc.ignorePvcPods},
|
||||
defaultevictor.DefaultEvictorArgs{IgnorePvcPods: tc.ignorePvcPods, NodeFit: tc.nodeFit},
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -804,3 +819,549 @@ func TestPodLifeTime_PodPhaseStates(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Tests for new fields (Conditions, ExitCodes, OwnerKinds, MinTimeSinceLastTransitionSeconds)
|
||||
// and extended States behavior (terminated reason matching)
|
||||
|
||||
func TestStatesWithTerminatedReasons(t *testing.T) {
|
||||
node := test.BuildTestNode("node1", 2000, 3000, 10, nil)
|
||||
testCases := []podLifeTimeTestCase{
|
||||
{
|
||||
description: "evict pod with matching terminated reason via states",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{"NodeAffinity"},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
p.Status.ContainerStatuses = []v1.ContainerStatus{
|
||||
{State: v1.ContainerState{Terminated: &v1.ContainerStateTerminated{Reason: "NodeAffinity"}}},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
},
|
||||
{
|
||||
description: "evict pod with matching pod status reason via states",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{"Shutdown"},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
p.Status.Reason = "Shutdown"
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
},
|
||||
{
|
||||
description: "states matches terminated reason on init container",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{"CreateContainerConfigError"},
|
||||
IncludingInitContainers: true,
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.InitContainerStatuses = []v1.ContainerStatus{
|
||||
{State: v1.ContainerState{Terminated: &v1.ContainerStateTerminated{Reason: "CreateContainerConfigError"}}},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
},
|
||||
{
|
||||
description: "states does not match terminated reason on init container without flag",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{"CreateContainerConfigError"},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.InitContainerStatuses = []v1.ContainerStatus{
|
||||
{State: v1.ContainerState{Terminated: &v1.ContainerStateTerminated{Reason: "CreateContainerConfigError"}}},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 0,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
runPodLifeTimeTest(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConditionFiltering(t *testing.T) {
|
||||
node := test.BuildTestNode("node1", 2000, 3000, 10, nil)
|
||||
testCases := []podLifeTimeTestCase{
|
||||
{
|
||||
description: "evict pod with matching condition reason",
|
||||
args: &PodLifeTimeArgs{
|
||||
Conditions: []PodConditionFilter{
|
||||
{Reason: "PodCompleted", Status: "True"},
|
||||
},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Conditions = []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "PodCompleted", LastTransitionTime: oldTransitionTime},
|
||||
}
|
||||
}),
|
||||
buildPod("p2", "node1", func(p *v1.Pod) {
|
||||
p.Status.Conditions = []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "OtherReason", LastTransitionTime: oldTransitionTime},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
expectedEvictedPods: []string{"p1"},
|
||||
},
|
||||
{
|
||||
description: "evict pod matching condition type only",
|
||||
args: &PodLifeTimeArgs{
|
||||
Conditions: []PodConditionFilter{
|
||||
{Type: string(v1.PodReady)},
|
||||
},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Conditions = []v1.PodCondition{
|
||||
{Type: v1.PodReady, Status: v1.ConditionFalse, LastTransitionTime: oldTransitionTime},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
},
|
||||
{
|
||||
description: "no matching conditions, 0 evictions",
|
||||
args: &PodLifeTimeArgs{
|
||||
Conditions: []PodConditionFilter{
|
||||
{Reason: "PodCompleted"},
|
||||
},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Conditions = []v1.PodCondition{
|
||||
{Type: v1.PodReady, Status: v1.ConditionTrue, Reason: "SomethingElse"},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 0,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
runPodLifeTimeTest(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransitionTimeFiltering(t *testing.T) {
|
||||
node := test.BuildTestNode("node1", 2000, 3000, 10, nil)
|
||||
var fiveMinutes uint = 300
|
||||
var fourHours uint = 14400
|
||||
|
||||
testCases := []podLifeTimeTestCase{
|
||||
{
|
||||
description: "evict pod with old transition time",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodSucceeded)},
|
||||
Conditions: []PodConditionFilter{
|
||||
{Reason: "PodCompleted", Status: "True", MinTimeSinceLastTransitionSeconds: &fiveMinutes},
|
||||
},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodSucceeded
|
||||
p.Status.Conditions = []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "PodCompleted", LastTransitionTime: oldTransitionTime},
|
||||
{Type: v1.PodReady, Status: v1.ConditionFalse, Reason: "PodCompleted", LastTransitionTime: oldTransitionTime},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
expectedEvictedPods: []string{"p1"},
|
||||
},
|
||||
{
|
||||
description: "do not evict pod with recent transition time",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodSucceeded)},
|
||||
Conditions: []PodConditionFilter{
|
||||
{MinTimeSinceLastTransitionSeconds: &fourHours},
|
||||
},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodSucceeded
|
||||
p.Status.Conditions = []v1.PodCondition{
|
||||
{Type: v1.PodReady, Status: v1.ConditionFalse, LastTransitionTime: newTransitionTime},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 0,
|
||||
},
|
||||
{
|
||||
description: "transition time scoped to matching conditions only",
|
||||
args: &PodLifeTimeArgs{
|
||||
Conditions: []PodConditionFilter{
|
||||
{Reason: "PodCompleted", MinTimeSinceLastTransitionSeconds: &fiveMinutes},
|
||||
},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Conditions = []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Reason: "PodCompleted", LastTransitionTime: oldTransitionTime},
|
||||
{Type: v1.PodReady, Reason: "OtherReason", LastTransitionTime: newTransitionTime},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
expectedEvictedPods: []string{"p1"},
|
||||
},
|
||||
{
|
||||
description: "no conditions on pod, transition time filter returns false",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodRunning)},
|
||||
Conditions: []PodConditionFilter{
|
||||
{MinTimeSinceLastTransitionSeconds: &fiveMinutes},
|
||||
},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodRunning
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 0,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
runPodLifeTimeTest(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOwnerKindsFiltering(t *testing.T) {
|
||||
node := test.BuildTestNode("node1", 2000, 3000, 10, nil)
|
||||
testCases := []podLifeTimeTestCase{
|
||||
{
|
||||
description: "exclude Job owner kind: Job-owned pod not evicted",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodFailed)},
|
||||
OwnerKinds: &OwnerKinds{Exclude: []string{"Job"}},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
p.ObjectMeta.OwnerReferences = []metav1.OwnerReference{{Kind: "Job", Name: "job1", APIVersion: "batch/v1"}}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 0,
|
||||
},
|
||||
{
|
||||
description: "exclude Job owner kind: non-Job pod still evicted",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodFailed)},
|
||||
OwnerKinds: &OwnerKinds{Exclude: []string{"Job"}},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
p.ObjectMeta.OwnerReferences = []metav1.OwnerReference{{Kind: "Job", Name: "job1", APIVersion: "batch/v1"}}
|
||||
}),
|
||||
buildPod("p2", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
expectedEvictedPods: []string{"p2"},
|
||||
},
|
||||
{
|
||||
description: "include only Job owner kind",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodFailed)},
|
||||
OwnerKinds: &OwnerKinds{Include: []string{"Job"}},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
p.ObjectMeta.OwnerReferences = []metav1.OwnerReference{{Kind: "Job", Name: "job1", APIVersion: "batch/v1"}}
|
||||
}),
|
||||
buildPod("p2", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
expectedEvictedPods: []string{"p1"},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
runPodLifeTimeTest(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatesWithEphemeralContainers(t *testing.T) {
|
||||
node := test.BuildTestNode("node1", 2000, 3000, 10, nil)
|
||||
testCases := []podLifeTimeTestCase{
|
||||
{
|
||||
description: "states matches terminated reason on ephemeral container",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{"OOMKilled"},
|
||||
IncludingEphemeralContainers: true,
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.EphemeralContainerStatuses = []v1.ContainerStatus{
|
||||
{State: v1.ContainerState{Terminated: &v1.ContainerStateTerminated{Reason: "OOMKilled"}}},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
},
|
||||
{
|
||||
description: "states does not match ephemeral container without flag",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{"OOMKilled"},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.EphemeralContainerStatuses = []v1.ContainerStatus{
|
||||
{State: v1.ContainerState{Terminated: &v1.ContainerStateTerminated{Reason: "OOMKilled"}}},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 0,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
runPodLifeTimeTest(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExitCodesFiltering(t *testing.T) {
|
||||
node := test.BuildTestNode("node1", 2000, 3000, 10, nil)
|
||||
testCases := []podLifeTimeTestCase{
|
||||
{
|
||||
description: "evict pod with matching exit code",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodFailed)},
|
||||
ExitCodes: []int32{1},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
p.Status.ContainerStatuses = []v1.ContainerStatus{
|
||||
{State: v1.ContainerState{Terminated: &v1.ContainerStateTerminated{ExitCode: 1}}},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
},
|
||||
{
|
||||
description: "exit code not matched, 0 evictions",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodFailed)},
|
||||
ExitCodes: []int32{2},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
p.Status.ContainerStatuses = []v1.ContainerStatus{
|
||||
{State: v1.ContainerState{Terminated: &v1.ContainerStateTerminated{ExitCode: 1}}},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 0,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
runPodLifeTimeTest(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCombinedFilters(t *testing.T) {
|
||||
node := test.BuildTestNode("node1", 2000, 3000, 10, nil)
|
||||
var fiveMinutes uint = 300
|
||||
|
||||
testCases := []podLifeTimeTestCase{
|
||||
{
|
||||
description: "user scenario: Succeeded + PodCompleted condition + transition time threshold",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodSucceeded)},
|
||||
Conditions: []PodConditionFilter{
|
||||
{Reason: "PodCompleted", Status: "True", MinTimeSinceLastTransitionSeconds: &fiveMinutes},
|
||||
},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("stale-completed", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodSucceeded
|
||||
p.Status.Conditions = []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "PodCompleted", LastTransitionTime: oldTransitionTime},
|
||||
{Type: v1.PodReady, Status: v1.ConditionFalse, Reason: "PodCompleted", LastTransitionTime: oldTransitionTime},
|
||||
}
|
||||
}),
|
||||
buildPod("recent-completed", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodSucceeded
|
||||
p.Status.Conditions = []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "PodCompleted", LastTransitionTime: newTransitionTime},
|
||||
}
|
||||
}),
|
||||
buildPod("running-pod", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodRunning
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
expectedEvictedPods: []string{"stale-completed"},
|
||||
},
|
||||
{
|
||||
description: "failed pod removal compat: Failed + min age + exclude Job",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodFailed)},
|
||||
MaxPodLifeTimeSeconds: utilptr.To[uint](0),
|
||||
OwnerKinds: &OwnerKinds{Exclude: []string{"Job"}},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("failed-rs", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
p.Status.ContainerStatuses = []v1.ContainerStatus{
|
||||
{State: v1.ContainerState{Terminated: &v1.ContainerStateTerminated{Reason: "Error"}}},
|
||||
}
|
||||
}),
|
||||
buildPod("failed-job", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
p.ObjectMeta.OwnerReferences = []metav1.OwnerReference{{Kind: "Job", Name: "job1", APIVersion: "batch/v1"}}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
expectedEvictedPods: []string{"failed-rs"},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
runPodLifeTimeTest(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation(t *testing.T) {
|
||||
testCases := []struct {
|
||||
description string
|
||||
args *PodLifeTimeArgs
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
description: "valid: states set",
|
||||
args: &PodLifeTimeArgs{States: []string{"Running"}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
description: "valid: conditions set",
|
||||
args: &PodLifeTimeArgs{Conditions: []PodConditionFilter{{Reason: "PodCompleted"}}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
description: "valid: maxPodLifeTimeSeconds set",
|
||||
args: &PodLifeTimeArgs{MaxPodLifeTimeSeconds: utilptr.To[uint](600)},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
description: "valid: states with maxPodLifeTimeSeconds",
|
||||
args: &PodLifeTimeArgs{States: []string{"Running"}, MaxPodLifeTimeSeconds: utilptr.To[uint](600)},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
description: "valid: states with conditions",
|
||||
args: &PodLifeTimeArgs{States: []string{"Succeeded"}, Conditions: []PodConditionFilter{{Reason: "PodCompleted"}}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
description: "valid: exitCodes only",
|
||||
args: &PodLifeTimeArgs{ExitCodes: []int32{1}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
description: "valid: condition with minTimeSinceLastTransitionSeconds",
|
||||
args: &PodLifeTimeArgs{
|
||||
Conditions: []PodConditionFilter{{Reason: "PodCompleted", MinTimeSinceLastTransitionSeconds: utilptr.To[uint](300)}},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
description: "valid: condition with only minTimeSinceLastTransitionSeconds",
|
||||
args: &PodLifeTimeArgs{
|
||||
Conditions: []PodConditionFilter{{MinTimeSinceLastTransitionSeconds: utilptr.To[uint](300)}},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
description: "invalid: no filter criteria",
|
||||
args: &PodLifeTimeArgs{},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
description: "invalid: both include and exclude namespaces",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{"Running"},
|
||||
Namespaces: &api.Namespaces{Include: []string{"a"}, Exclude: []string{"b"}},
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
description: "invalid: both include and exclude ownerKinds",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{"Running"},
|
||||
OwnerKinds: &OwnerKinds{Include: []string{"Job"}, Exclude: []string{"ReplicaSet"}},
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
description: "invalid: bad state name",
|
||||
args: &PodLifeTimeArgs{States: []string{"NotAState"}},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
description: "invalid: empty condition filter",
|
||||
args: &PodLifeTimeArgs{
|
||||
Conditions: []PodConditionFilter{{}},
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
err := ValidatePodLifeTimeArgs(tc.args)
|
||||
if tc.expectError && err == nil {
|
||||
t.Error("Expected validation error but got nil")
|
||||
}
|
||||
if !tc.expectError && err != nil {
|
||||
t.Errorf("Expected no validation error but got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,50 @@ import (
|
||||
type PodLifeTimeArgs struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
Namespaces *api.Namespaces `json:"namespaces,omitempty"`
|
||||
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
|
||||
MaxPodLifeTimeSeconds *uint `json:"maxPodLifeTimeSeconds,omitempty"`
|
||||
States []string `json:"states,omitempty"`
|
||||
IncludingInitContainers bool `json:"includingInitContainers,omitempty"`
|
||||
IncludingEphemeralContainers bool `json:"includingEphemeralContainers,omitempty"`
|
||||
Namespaces *api.Namespaces `json:"namespaces,omitempty"`
|
||||
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
|
||||
OwnerKinds *OwnerKinds `json:"ownerKinds,omitempty"`
|
||||
|
||||
MaxPodLifeTimeSeconds *uint `json:"maxPodLifeTimeSeconds,omitempty"`
|
||||
|
||||
// States filters pods by phase, pod status reason, container waiting reason,
|
||||
// or container terminated reason. A pod matches if any of its states appear
|
||||
// in this list.
|
||||
States []string `json:"states,omitempty"`
|
||||
|
||||
// Conditions filters pods by status.conditions entries. A pod matches if
|
||||
// any of its conditions satisfy at least one filter. Each filter can
|
||||
// optionally require a minimum time since the condition last transitioned.
|
||||
Conditions []PodConditionFilter `json:"conditions,omitempty"`
|
||||
|
||||
// ExitCodes filters by container terminated exit codes.
|
||||
ExitCodes []int32 `json:"exitCodes,omitempty"`
|
||||
|
||||
IncludingInitContainers bool `json:"includingInitContainers,omitempty"`
|
||||
IncludingEphemeralContainers bool `json:"includingEphemeralContainers,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// OwnerKinds allows filtering pods by owner reference kinds with include/exclude support.
|
||||
// At most one of Include/Exclude may be set.
|
||||
type OwnerKinds struct {
|
||||
Include []string `json:"include,omitempty"`
|
||||
Exclude []string `json:"exclude,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// PodConditionFilter matches a pod condition by type, status, and/or reason.
|
||||
// All specified fields must match (AND). Unset fields are not checked.
|
||||
// When MinTimeSinceLastTransitionSeconds is set, the condition must also have
|
||||
// a lastTransitionTime older than this many seconds.
|
||||
type PodConditionFilter struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
|
||||
// MinTimeSinceLastTransitionSeconds requires the matching condition's
|
||||
// lastTransitionTime to be at least this many seconds in the past.
|
||||
MinTimeSinceLastTransitionSeconds *uint `json:"minTimeSinceLastTransitionSeconds,omitempty"`
|
||||
}
|
||||
|
||||
@@ -27,57 +27,78 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
var podLifeTimeAllowedStates = sets.New(
|
||||
// Pod Status Phase
|
||||
string(v1.PodRunning),
|
||||
string(v1.PodPending),
|
||||
string(v1.PodSucceeded),
|
||||
string(v1.PodFailed),
|
||||
string(v1.PodUnknown),
|
||||
|
||||
// Pod Status Reasons
|
||||
"NodeAffinity",
|
||||
"NodeLost",
|
||||
"Shutdown",
|
||||
"UnexpectedAdmissionError",
|
||||
|
||||
// Container State Waiting Reasons
|
||||
"PodInitializing",
|
||||
"ContainerCreating",
|
||||
"ImagePullBackOff",
|
||||
"CrashLoopBackOff",
|
||||
"CreateContainerConfigError",
|
||||
"ErrImagePull",
|
||||
"CreateContainerError",
|
||||
"InvalidImageName",
|
||||
|
||||
// Container State Terminated Reasons
|
||||
"OOMKilled",
|
||||
"Error",
|
||||
"Completed",
|
||||
"DeadlineExceeded",
|
||||
"Evicted",
|
||||
"ContainerCannotRun",
|
||||
"StartError",
|
||||
)
|
||||
|
||||
// ValidatePodLifeTimeArgs validates PodLifeTime arguments
|
||||
func ValidatePodLifeTimeArgs(obj runtime.Object) error {
|
||||
args := obj.(*PodLifeTimeArgs)
|
||||
var allErrs []error
|
||||
if args.MaxPodLifeTimeSeconds == nil {
|
||||
allErrs = append(allErrs, fmt.Errorf("MaxPodLifeTimeSeconds not set"))
|
||||
}
|
||||
|
||||
// At most one of include/exclude can be set
|
||||
if args.Namespaces != nil && len(args.Namespaces.Include) > 0 && len(args.Namespaces.Exclude) > 0 {
|
||||
allErrs = append(allErrs, fmt.Errorf("only one of Include/Exclude namespaces can be set"))
|
||||
}
|
||||
|
||||
if args.OwnerKinds != nil && len(args.OwnerKinds.Include) > 0 && len(args.OwnerKinds.Exclude) > 0 {
|
||||
allErrs = append(allErrs, fmt.Errorf("only one of Include/Exclude ownerKinds can be set"))
|
||||
}
|
||||
|
||||
if args.LabelSelector != nil {
|
||||
if _, err := metav1.LabelSelectorAsSelector(args.LabelSelector); err != nil {
|
||||
allErrs = append(allErrs, fmt.Errorf("failed to get label selectors from strategy's params: %+v", err))
|
||||
}
|
||||
}
|
||||
podLifeTimeAllowedStates := sets.New(
|
||||
// Pod Status Phase
|
||||
string(v1.PodRunning),
|
||||
string(v1.PodPending),
|
||||
string(v1.PodSucceeded),
|
||||
string(v1.PodFailed),
|
||||
string(v1.PodUnknown),
|
||||
|
||||
// Pod Status Reasons
|
||||
"NodeAffinity",
|
||||
"NodeLost",
|
||||
"Shutdown",
|
||||
"UnexpectedAdmissionError",
|
||||
|
||||
// Container Status Reasons
|
||||
// Container state reasons: https://github.com/kubernetes/kubernetes/blob/release-1.24/pkg/kubelet/kubelet_pods.go#L76-L79
|
||||
"PodInitializing",
|
||||
"ContainerCreating",
|
||||
|
||||
// containerStatuses[*].state.waiting.reason: ImagePullBackOff, etc.
|
||||
"ImagePullBackOff",
|
||||
"CrashLoopBackOff",
|
||||
"CreateContainerConfigError",
|
||||
"ErrImagePull",
|
||||
"CreateContainerError",
|
||||
"InvalidImageName",
|
||||
)
|
||||
|
||||
if !podLifeTimeAllowedStates.HasAll(args.States...) {
|
||||
if len(args.States) > 0 && !podLifeTimeAllowedStates.HasAll(args.States...) {
|
||||
allowed := podLifeTimeAllowedStates.UnsortedList()
|
||||
sort.Strings(allowed)
|
||||
allErrs = append(allErrs, fmt.Errorf("states must be one of %v", allowed))
|
||||
}
|
||||
|
||||
for i, c := range args.Conditions {
|
||||
if c.Type == "" && c.Status == "" && c.Reason == "" && c.MinTimeSinceLastTransitionSeconds == nil {
|
||||
allErrs = append(allErrs, fmt.Errorf("conditions[%d]: at least one of type, status, reason, or minTimeSinceLastTransitionSeconds must be set", i))
|
||||
}
|
||||
}
|
||||
|
||||
hasFilter := args.MaxPodLifeTimeSeconds != nil ||
|
||||
len(args.States) > 0 ||
|
||||
len(args.Conditions) > 0 ||
|
||||
len(args.ExitCodes) > 0
|
||||
if !hasFilter {
|
||||
allErrs = append(allErrs, fmt.Errorf("at least one filtering criterion must be specified (maxPodLifeTimeSeconds, states, conditions, or exitCodes)"))
|
||||
}
|
||||
|
||||
return utilerrors.NewAggregate(allErrs)
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ func TestValidateRemovePodLifeTimeArgs(t *testing.T) {
|
||||
args: &PodLifeTimeArgs{
|
||||
MaxPodLifeTimeSeconds: nil,
|
||||
},
|
||||
errInfo: fmt.Errorf("MaxPodLifeTimeSeconds not set"),
|
||||
errInfo: fmt.Errorf("at least one filtering criterion must be specified (maxPodLifeTimeSeconds, states, conditions, or exitCodes)"),
|
||||
},
|
||||
{
|
||||
description: "invalid pod state arg, expects errors",
|
||||
@@ -63,7 +63,7 @@ func TestValidateRemovePodLifeTimeArgs(t *testing.T) {
|
||||
MaxPodLifeTimeSeconds: func(i uint) *uint { return &i }(1),
|
||||
States: []string{string("InvalidState")},
|
||||
},
|
||||
errInfo: fmt.Errorf("states must be one of [ContainerCreating CrashLoopBackOff CreateContainerConfigError CreateContainerError ErrImagePull Failed ImagePullBackOff InvalidImageName NodeAffinity NodeLost Pending PodInitializing Running Shutdown Succeeded UnexpectedAdmissionError Unknown]"),
|
||||
errInfo: fmt.Errorf("states must be one of [Completed ContainerCannotRun ContainerCreating CrashLoopBackOff CreateContainerConfigError CreateContainerError DeadlineExceeded ErrImagePull Error Evicted Failed ImagePullBackOff InvalidImageName NodeAffinity NodeLost OOMKilled Pending PodInitializing Running Shutdown StartError Succeeded UnexpectedAdmissionError Unknown]"),
|
||||
},
|
||||
{
|
||||
description: "nil MaxPodLifeTimeSeconds arg and invalid pod state arg, expects errors",
|
||||
@@ -71,7 +71,7 @@ func TestValidateRemovePodLifeTimeArgs(t *testing.T) {
|
||||
MaxPodLifeTimeSeconds: nil,
|
||||
States: []string{string("InvalidState")},
|
||||
},
|
||||
errInfo: fmt.Errorf("[MaxPodLifeTimeSeconds not set, states must be one of [ContainerCreating CrashLoopBackOff CreateContainerConfigError CreateContainerError ErrImagePull Failed ImagePullBackOff InvalidImageName NodeAffinity NodeLost Pending PodInitializing Running Shutdown Succeeded UnexpectedAdmissionError Unknown]]"),
|
||||
errInfo: fmt.Errorf("states must be one of [Completed ContainerCannotRun ContainerCreating CrashLoopBackOff CreateContainerConfigError CreateContainerError DeadlineExceeded ErrImagePull Error Evicted Failed ImagePullBackOff InvalidImageName NodeAffinity NodeLost OOMKilled Pending PodInitializing Running Shutdown StartError Succeeded UnexpectedAdmissionError Unknown]"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,53 @@ import (
|
||||
api "sigs.k8s.io/descheduler/pkg/api"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OwnerKinds) DeepCopyInto(out *OwnerKinds) {
|
||||
*out = *in
|
||||
if in.Include != nil {
|
||||
in, out := &in.Include, &out.Include
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Exclude != nil {
|
||||
in, out := &in.Exclude, &out.Exclude
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnerKinds.
|
||||
func (in *OwnerKinds) DeepCopy() *OwnerKinds {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(OwnerKinds)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PodConditionFilter) DeepCopyInto(out *PodConditionFilter) {
|
||||
*out = *in
|
||||
if in.MinTimeSinceLastTransitionSeconds != nil {
|
||||
in, out := &in.MinTimeSinceLastTransitionSeconds, &out.MinTimeSinceLastTransitionSeconds
|
||||
*out = new(uint)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodConditionFilter.
|
||||
func (in *PodConditionFilter) DeepCopy() *PodConditionFilter {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PodConditionFilter)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PodLifeTimeArgs) DeepCopyInto(out *PodLifeTimeArgs) {
|
||||
*out = *in
|
||||
@@ -41,6 +88,11 @@ func (in *PodLifeTimeArgs) DeepCopyInto(out *PodLifeTimeArgs) {
|
||||
*out = new(v1.LabelSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.OwnerKinds != nil {
|
||||
in, out := &in.OwnerKinds, &out.OwnerKinds
|
||||
*out = new(OwnerKinds)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.MaxPodLifeTimeSeconds != nil {
|
||||
in, out := &in.MaxPodLifeTimeSeconds, &out.MaxPodLifeTimeSeconds
|
||||
*out = new(uint)
|
||||
@@ -51,6 +103,18 @@ func (in *PodLifeTimeArgs) DeepCopyInto(out *PodLifeTimeArgs) {
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]PodConditionFilter, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.ExitCodes != nil {
|
||||
in, out := &in.ExitCodes, &out.ExitCodes
|
||||
*out = make([]int32, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -84,19 +84,13 @@ func New(ctx context.Context, args runtime.Object, handle frameworktypes.Handle)
|
||||
if states.Has(string(pod.Status.Phase)) {
|
||||
return true
|
||||
}
|
||||
|
||||
containerStatuses := pod.Status.ContainerStatuses
|
||||
|
||||
if tooManyRestartsArgs.IncludingInitContainers {
|
||||
containerStatuses = append(containerStatuses, pod.Status.InitContainerStatuses...)
|
||||
if podutil.HasMatchingContainerWaitingState(pod.Status.ContainerStatuses, states) {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, containerStatus := range containerStatuses {
|
||||
if containerStatus.State.Waiting != nil && states.Has(containerStatus.State.Waiting.Reason) {
|
||||
return true
|
||||
}
|
||||
if tooManyRestartsArgs.IncludingInitContainers &&
|
||||
podutil.HasMatchingContainerWaitingState(pod.Status.InitContainerStatuses, states) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
sdkresource "go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
@@ -189,52 +189,10 @@ func TestRemoveDuplicates(t *testing.T) {
|
||||
tc.removeDuplicatesArgs.Namespaces = &api.Namespaces{
|
||||
Include: []string{testNamespace.Name},
|
||||
}
|
||||
deschedulerPolicyConfigMapObj, err := deschedulerPolicyConfigMap(removeDuplicatesPolicy(tc.removeDuplicatesArgs, tc.evictorArgs))
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
|
||||
t.Logf("Creating %q policy CM with RemoveDuplicates configured...", deschedulerPolicyConfigMapObj.Name)
|
||||
_, err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Create(ctx, deschedulerPolicyConfigMapObj, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
t.Logf("Deleting %q CM...", deschedulerPolicyConfigMapObj.Name)
|
||||
err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Delete(ctx, deschedulerPolicyConfigMapObj.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to delete %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
}()
|
||||
createPolicyConfigMap(t, ctx, clientSet, removeDuplicatesPolicy(tc.removeDuplicatesArgs, tc.evictorArgs))
|
||||
|
||||
deschedulerDeploymentObj := deschedulerDeployment(testNamespace.Name)
|
||||
t.Logf("Creating descheduler deployment %v", deschedulerDeploymentObj.Name)
|
||||
_, err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Create(ctx, deschedulerDeploymentObj, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q deployment: %v", deschedulerDeploymentObj.Name, err)
|
||||
}
|
||||
|
||||
deschedulerPodName := ""
|
||||
defer func() {
|
||||
if deschedulerPodName != "" {
|
||||
printPodLogs(ctx, t, clientSet, deschedulerPodName)
|
||||
}
|
||||
|
||||
t.Logf("Deleting %q deployment...", deschedulerDeploymentObj.Name)
|
||||
err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(ctx, deschedulerDeploymentObj.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to delete %q deployment: %v", deschedulerDeploymentObj.Name, err)
|
||||
}
|
||||
|
||||
waitForPodsToDisappear(ctx, t, clientSet, deschedulerDeploymentObj.Labels, deschedulerDeploymentObj.Namespace)
|
||||
}()
|
||||
|
||||
t.Logf("Waiting for the descheduler pod running")
|
||||
deschedulerPods := waitForPodsRunning(ctx, t, clientSet, deschedulerDeploymentObj.Labels, 1, deschedulerDeploymentObj.Namespace)
|
||||
if len(deschedulerPods) != 0 {
|
||||
deschedulerPodName = deschedulerPods[0].Name
|
||||
}
|
||||
createDeschedulerDeploymentWithCleanup(t, ctx, clientSet, deschedulerDeploymentObj)
|
||||
|
||||
// Run RemoveDuplicates strategy
|
||||
var meetsExpectations bool
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
utilptr "k8s.io/utils/ptr"
|
||||
|
||||
kvcorev1 "kubevirt.io/api/core/v1"
|
||||
generatedclient "kubevirt.io/client-go/generated/kubevirt/clientset/versioned"
|
||||
kubevirtclient "kubevirt.io/client-go/kubevirt"
|
||||
|
||||
"sigs.k8s.io/descheduler/pkg/api"
|
||||
apiv1alpha2 "sigs.k8s.io/descheduler/pkg/api/v1alpha2"
|
||||
@@ -34,12 +34,16 @@ import (
|
||||
|
||||
const (
|
||||
vmiCount = 3
|
||||
// virtLauncherSelector selects KubeVirt virt-launcher pods only,
|
||||
// avoiding listing unrelated pods in the namespace.
|
||||
virtLauncherSelector = "kubevirt.io=virt-launcher"
|
||||
)
|
||||
|
||||
func virtualMachineInstance(idx int) *kvcorev1.VirtualMachineInstance {
|
||||
func virtualMachineInstance(idx int, namespace string) *kvcorev1.VirtualMachineInstance {
|
||||
return &kvcorev1.VirtualMachineInstance{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("kubevirtvmi-%v", idx),
|
||||
Name: fmt.Sprintf("kubevirtvmi-%v", idx),
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
"descheduler.alpha.kubernetes.io/request-evict-only": "",
|
||||
},
|
||||
@@ -58,20 +62,12 @@ func virtualMachineInstance(idx int) *kvcorev1.VirtualMachineInstance {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "cloudinitdisk",
|
||||
DiskDevice: kvcorev1.DiskDevice{
|
||||
Disk: &kvcorev1.DiskTarget{
|
||||
Bus: kvcorev1.DiskBusVirtio,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Rng: &kvcorev1.Rng{},
|
||||
},
|
||||
Resources: kvcorev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
corev1.ResourceMemory: resource.MustParse("1024M"),
|
||||
corev1.ResourceMemory: resource.MustParse("128M"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -81,27 +77,7 @@ func virtualMachineInstance(idx int) *kvcorev1.VirtualMachineInstance {
|
||||
Name: "containerdisk",
|
||||
VolumeSource: kvcorev1.VolumeSource{
|
||||
ContainerDisk: &kvcorev1.ContainerDiskSource{
|
||||
Image: "quay.io/kubevirt/fedora-with-test-tooling-container-disk:20240710_1265d1090",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "cloudinitdisk",
|
||||
VolumeSource: kvcorev1.VolumeSource{
|
||||
CloudInitNoCloud: &kvcorev1.CloudInitNoCloudSource{
|
||||
UserData: `#cloud-config
|
||||
password: fedora
|
||||
chpasswd: { expire: False }
|
||||
packages:
|
||||
- nginx
|
||||
runcmd:
|
||||
- [ "systemctl", "enable", "--now", "nginx" ]`,
|
||||
NetworkData: `version: 2
|
||||
ethernets:
|
||||
eth0:
|
||||
addresses: [ fd10:0:2::2/120 ]
|
||||
dhcp4: true
|
||||
gateway6: fd10:0:2::1`,
|
||||
Image: kubevirtCirrosContainerDiskImage(),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -110,31 +86,83 @@ ethernets:
|
||||
}
|
||||
}
|
||||
|
||||
func waitForKubevirtReady(t *testing.T, ctx context.Context, kvClient generatedclient.Interface) {
|
||||
obj, err := kvClient.KubevirtV1().KubeVirts("kubevirt").Get(ctx, "kubevirt", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to get kubevirt/kubevirt: %v", err)
|
||||
}
|
||||
available := false
|
||||
for _, condition := range obj.Status.Conditions {
|
||||
if condition.Type == kvcorev1.KubeVirtConditionAvailable {
|
||||
if condition.Status == corev1.ConditionTrue {
|
||||
available = true
|
||||
func formatContainerStatuses(pod *corev1.Pod) string {
|
||||
var parts []string
|
||||
formatList := func(prefix string, statuses []corev1.ContainerStatus) {
|
||||
for _, cs := range statuses {
|
||||
stateStr := "unknown"
|
||||
if cs.State.Waiting != nil {
|
||||
stateStr = fmt.Sprintf("waiting(reason=%q, message=%q)", cs.State.Waiting.Reason, cs.State.Waiting.Message)
|
||||
} else if cs.State.Terminated != nil {
|
||||
stateStr = fmt.Sprintf("terminated(exitCode=%d, reason=%q, message=%q)", cs.State.Terminated.ExitCode, cs.State.Terminated.Reason, cs.State.Terminated.Message)
|
||||
} else if cs.State.Running != nil {
|
||||
stateStr = "running"
|
||||
}
|
||||
parts = append(parts, fmt.Sprintf("%s%s: state=%s, ready=%v, restarts=%d", prefix, cs.Name, stateStr, cs.Ready, cs.RestartCount))
|
||||
}
|
||||
}
|
||||
if !available {
|
||||
t.Fatalf("Kubevirt is not available")
|
||||
formatList("init:", pod.Status.InitContainerStatuses)
|
||||
formatList("", pod.Status.ContainerStatuses)
|
||||
return strings.Join(parts, "; ")
|
||||
}
|
||||
|
||||
// ensureVMIsLiveMigratable waits until every VMI reports the LiveMigratable condition with status True.
|
||||
func ensureVMIsLiveMigratable(t *testing.T, ctx context.Context, kvClient kubevirtclient.Interface, namespace string) {
|
||||
t.Helper()
|
||||
err := wait.PollUntilContextTimeout(ctx, 3*time.Second, 180*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
vmiList, err := kvClient.KubevirtV1().VirtualMachineInstances(namespace).List(ctx, metav1.ListOptions{})
|
||||
if err != nil || len(vmiList.Items) != vmiCount {
|
||||
return false, nil
|
||||
}
|
||||
for _, vmi := range vmiList.Items {
|
||||
migratable := false
|
||||
for _, c := range vmi.Status.Conditions {
|
||||
if c.Type == kvcorev1.VirtualMachineInstanceIsMigratable && c.Status == corev1.ConditionTrue {
|
||||
migratable = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !migratable {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("VMIs never became LiveMigratable: %v", err)
|
||||
}
|
||||
klog.Infof("All VMIs are LiveMigratable")
|
||||
}
|
||||
|
||||
func waitForKubevirtReady(t *testing.T, ctx context.Context, kvClient kubevirtclient.Interface) {
|
||||
t.Helper()
|
||||
err := wait.PollUntilContextTimeout(ctx, 3*time.Second, 180*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
obj, err := kvClient.KubevirtV1().KubeVirts("kubevirt").Get(ctx, "kubevirt", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
klog.Infof("Unable to get kubevirt/kubevirt: %v", err)
|
||||
return false, nil
|
||||
}
|
||||
for _, condition := range obj.Status.Conditions {
|
||||
if condition.Type == kvcorev1.KubeVirtConditionAvailable && condition.Status == corev1.ConditionTrue {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Kubevirt is not available: %v", err)
|
||||
}
|
||||
klog.Infof("Kubevirt is available")
|
||||
}
|
||||
|
||||
func allVMIsHaveRunningPods(t *testing.T, ctx context.Context, kubeClient clientset.Interface, kvClient generatedclient.Interface) (bool, error) {
|
||||
func allVMIsHaveRunningPods(t *testing.T, ctx context.Context, kubeClient clientset.Interface, kvClient kubevirtclient.Interface, namespace string) (bool, error) {
|
||||
klog.Infof("Checking all vmi active pods are running")
|
||||
uidMap := make(map[types.UID]*corev1.Pod)
|
||||
podList, err := kubeClient.CoreV1().Pods("default").List(ctx, metav1.ListOptions{})
|
||||
podList, err := kubeClient.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
|
||||
LabelSelector: virtLauncherSelector,
|
||||
})
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "client rate limiter") {
|
||||
if isClientRateLimiterError(err) {
|
||||
klog.Infof("Unable to list pods: %v", err)
|
||||
return false, nil
|
||||
}
|
||||
@@ -148,7 +176,7 @@ func allVMIsHaveRunningPods(t *testing.T, ctx context.Context, kubeClient client
|
||||
uidMap[item.UID] = &pod
|
||||
}
|
||||
|
||||
vmiList, err := kvClient.KubevirtV1().VirtualMachineInstances("default").List(ctx, metav1.ListOptions{})
|
||||
vmiList, err := kvClient.KubevirtV1().VirtualMachineInstances(namespace).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
klog.Infof("Unable to list VMIs: %v", err)
|
||||
return false, err
|
||||
@@ -165,14 +193,19 @@ func allVMIsHaveRunningPods(t *testing.T, ctx context.Context, kubeClient client
|
||||
klog.Infof("Active pod %v not found", activePod)
|
||||
return false, nil
|
||||
}
|
||||
klog.Infof("Checking whether active pod %v (uid=%v) is running", uidMap[activePod].Name, activePod)
|
||||
// ignore completed/failed pods
|
||||
if uidMap[activePod].Status.Phase == corev1.PodFailed || uidMap[activePod].Status.Phase == corev1.PodSucceeded {
|
||||
klog.Infof("Ignoring active pod %v, phase=%v", uidMap[activePod].Name, uidMap[activePod].Status.Phase)
|
||||
pod := uidMap[activePod]
|
||||
klog.Infof("Checking whether active pod %v (uid=%v) is running", pod.Name, activePod)
|
||||
if pod.Status.Phase == corev1.PodFailed {
|
||||
details := fmt.Sprintf("pod %s (phase=Failed, reason=%q, message=%q, containers=[%s])", pod.Name, pod.Status.Reason, pod.Status.Message, formatContainerStatuses(pod))
|
||||
klog.Infof("Active pod failed: %s", details)
|
||||
continue
|
||||
}
|
||||
if uidMap[activePod].Status.Phase != corev1.PodRunning {
|
||||
klog.Infof("activePod %v is not running: %v\n", uidMap[activePod].Name, uidMap[activePod].Status.Phase)
|
||||
if pod.Status.Phase == corev1.PodSucceeded {
|
||||
klog.Infof("Ignoring active pod %v, phase=%v", pod.Name, pod.Status.Phase)
|
||||
continue
|
||||
}
|
||||
if pod.Status.Phase != corev1.PodRunning {
|
||||
klog.Infof("activePod %v is not running: %v\n", pod.Name, pod.Status.Phase)
|
||||
return false, nil
|
||||
}
|
||||
atLeastOneVmiIsRunning = true
|
||||
@@ -186,7 +219,7 @@ func allVMIsHaveRunningPods(t *testing.T, ctx context.Context, kubeClient client
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func podLifeTimePolicy() *apiv1alpha2.DeschedulerPolicy {
|
||||
func podLifeTimePolicy(namespace string) *apiv1alpha2.DeschedulerPolicy {
|
||||
return &apiv1alpha2.DeschedulerPolicy{
|
||||
Profiles: []apiv1alpha2.DeschedulerProfile{
|
||||
{
|
||||
@@ -198,7 +231,7 @@ func podLifeTimePolicy() *apiv1alpha2.DeschedulerPolicy {
|
||||
Object: &podlifetime.PodLifeTimeArgs{
|
||||
MaxPodLifeTimeSeconds: utilptr.To[uint](1), // set it to immediate eviction
|
||||
Namespaces: &api.Namespaces{
|
||||
Include: []string{"default"},
|
||||
Include: []string{namespace},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -229,10 +262,12 @@ func podLifeTimePolicy() *apiv1alpha2.DeschedulerPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
func kVirtRunningPodNames(t *testing.T, ctx context.Context, kubeClient clientset.Interface) []string {
|
||||
func kVirtRunningPodNames(t *testing.T, ctx context.Context, kubeClient clientset.Interface, namespace string) []string {
|
||||
names := []string{}
|
||||
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
podList, err := kubeClient.CoreV1().Pods("default").List(ctx, metav1.ListOptions{})
|
||||
podList, err := kubeClient.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
|
||||
LabelSelector: virtLauncherSelector,
|
||||
})
|
||||
if err != nil {
|
||||
if isClientRateLimiterError(err) {
|
||||
t.Log(err)
|
||||
@@ -243,9 +278,6 @@ func kVirtRunningPodNames(t *testing.T, ctx context.Context, kubeClient clientse
|
||||
}
|
||||
|
||||
for _, item := range podList.Items {
|
||||
if !strings.HasPrefix(item.Name, "virt-launcher-kubevirtvmi-") {
|
||||
t.Fatalf("Only pod names with 'virt-launcher-kubevirtvmi-' prefix are expected, got %q instead", item.Name)
|
||||
}
|
||||
if item.Status.Phase == corev1.PodRunning {
|
||||
names = append(names, item.Name)
|
||||
}
|
||||
@@ -258,13 +290,13 @@ func kVirtRunningPodNames(t *testing.T, ctx context.Context, kubeClient clientse
|
||||
return names
|
||||
}
|
||||
|
||||
func observeLiveMigration(t *testing.T, ctx context.Context, kubeClient clientset.Interface, usedRunningPodNames map[string]struct{}) {
|
||||
func observeLiveMigration(t *testing.T, ctx context.Context, kubeClient clientset.Interface, namespace string, usedRunningPodNames map[string]struct{}) {
|
||||
prevTotal := uint(0)
|
||||
jumps := 0
|
||||
// keep running the descheduling cycle until the migration is triggered and completed few times or times out
|
||||
for i := 0; i < 240; i++ {
|
||||
// monitor how many pods get evicted
|
||||
names := kVirtRunningPodNames(t, ctx, kubeClient)
|
||||
names := kVirtRunningPodNames(t, ctx, kubeClient, namespace)
|
||||
klog.Infof("vmi pods: %#v\n", names)
|
||||
// The number of pods need to be kept between vmiCount and vmiCount+1.
|
||||
// At most two pods are expected to have virt-launcher-kubevirtvmi-X prefix name in common.
|
||||
@@ -299,16 +331,18 @@ func observeLiveMigration(t *testing.T, ctx context.Context, kubeClient clientse
|
||||
if prevTotal != 0 && prevTotal != total {
|
||||
jumps++
|
||||
}
|
||||
// Expect at least 3 finished live migrations (two should be enough as well, though ...)
|
||||
if jumps >= 6 {
|
||||
// Expect at least 2 finished live migrations
|
||||
if jumps >= 4 {
|
||||
break
|
||||
}
|
||||
prevTotal = total
|
||||
time.Sleep(time.Second)
|
||||
time.Sleep(4 * time.Second)
|
||||
}
|
||||
|
||||
if jumps < 6 {
|
||||
podList, err := kubeClient.CoreV1().Pods("default").List(ctx, metav1.ListOptions{})
|
||||
if jumps < 4 {
|
||||
podList, err := kubeClient.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
|
||||
LabelSelector: virtLauncherSelector,
|
||||
})
|
||||
if err != nil {
|
||||
klog.Infof("Unable to list pods: %v", err)
|
||||
} else {
|
||||
@@ -317,9 +351,9 @@ func observeLiveMigration(t *testing.T, ctx context.Context, kubeClient clientse
|
||||
}
|
||||
}
|
||||
|
||||
t.Fatalf("Expected at least 3 finished live migrations, got less: %v", jumps/2.0)
|
||||
t.Fatalf("Expected at least 2 finished live migrations, got less: %v", jumps/2.0)
|
||||
}
|
||||
klog.Infof("The live migration finished 3 times")
|
||||
klog.Infof("The live migration finished 2 times")
|
||||
|
||||
// len(usedRunningPodNames) is expected to be vmiCount + jumps/2 + 1 (one more live migration could still be initiated)
|
||||
klog.Infof("len(usedRunningPodNames): %v, upper limit: %v\n", len(usedRunningPodNames), vmiCount+jumps/2+1)
|
||||
@@ -328,7 +362,7 @@ func observeLiveMigration(t *testing.T, ctx context.Context, kubeClient clientse
|
||||
}
|
||||
|
||||
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
names := kVirtRunningPodNames(t, ctx, kubeClient)
|
||||
names := kVirtRunningPodNames(t, ctx, kubeClient, namespace)
|
||||
klog.Infof("vmi pods: %#v\n", names)
|
||||
lNames := len(names)
|
||||
if lNames != vmiCount {
|
||||
@@ -346,7 +380,13 @@ func createAndWaitForDeschedulerRunning(t *testing.T, ctx context.Context, kubeC
|
||||
klog.Infof("Creating descheduler deployment %v", deschedulerDeploymentObj.Name)
|
||||
_, err := kubeClient.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Create(ctx, deschedulerDeploymentObj, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q deployment: %v", deschedulerDeploymentObj.Name, err)
|
||||
if apierrors.IsAlreadyExists(err) {
|
||||
_ = kubeClient.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(ctx, deschedulerDeploymentObj.Name, metav1.DeleteOptions{})
|
||||
_, err = kubeClient.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Create(ctx, deschedulerDeploymentObj, metav1.CreateOptions{})
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q deployment: %v", deschedulerDeploymentObj.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
klog.Infof("Waiting for the descheduler pod running")
|
||||
@@ -368,7 +408,7 @@ func updateDeschedulerPolicy(t *testing.T, ctx context.Context, kubeClient clien
|
||||
}
|
||||
}
|
||||
|
||||
func createKubevirtClient() (generatedclient.Interface, error) {
|
||||
func createKubevirtClient() (kubevirtclient.Interface, error) {
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
|
||||
overrides := &clientcmd.ConfigOverrides{}
|
||||
@@ -382,146 +422,49 @@ func createKubevirtClient() (generatedclient.Interface, error) {
|
||||
config.APIPath = "/apis"
|
||||
config.ContentType = runtime.ContentTypeJSON
|
||||
|
||||
return generatedclient.NewForConfig(config)
|
||||
return kubevirtclient.NewForConfig(config)
|
||||
}
|
||||
|
||||
func TestLiveMigrationInBackground(t *testing.T) {
|
||||
initPluginRegistry()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
kubeClient, err := client.CreateClient(componentbaseconfig.ClientConnectionConfiguration{Kubeconfig: os.Getenv("KUBECONFIG")}, "")
|
||||
if err != nil {
|
||||
t.Fatalf("Error during kubernetes client creation with %v", err)
|
||||
func setupE2ELiveMigrationNamespace(t *testing.T, ctx context.Context, kubeClient clientset.Interface, kvClient kubevirtclient.Interface, vmiNamespace string) {
|
||||
t.Helper()
|
||||
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: vmiNamespace}}
|
||||
if _, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}); err != nil && !apierrors.IsAlreadyExists(err) {
|
||||
t.Fatalf("Unable to create namespace %v: %v", vmiNamespace, err)
|
||||
}
|
||||
|
||||
kvClient, err := createKubevirtClient()
|
||||
if err != nil {
|
||||
t.Fatalf("Error during kvClient creation with %v", err)
|
||||
}
|
||||
|
||||
waitForKubevirtReady(t, ctx, kvClient)
|
||||
|
||||
// Delete all VMIs
|
||||
defer func() {
|
||||
t.Cleanup(func() {
|
||||
cleanupCtx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
for i := 1; i <= vmiCount; i++ {
|
||||
vmi := virtualMachineInstance(i)
|
||||
err := kvClient.KubevirtV1().VirtualMachineInstances("default").Delete(context.Background(), vmi.Name, metav1.DeleteOptions{})
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
vmi := virtualMachineInstance(i, vmiNamespace)
|
||||
if err := kvClient.KubevirtV1().VirtualMachineInstances(vmiNamespace).Delete(cleanupCtx, vmi.Name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
|
||||
klog.Infof("Unable to delete vmi %v: %v", vmi.Name, err)
|
||||
}
|
||||
}
|
||||
wait.PollUntilContextTimeout(ctx, 5*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
podList, err := kubeClient.CoreV1().Pods("default").List(ctx, metav1.ListOptions{})
|
||||
wait.PollUntilContextTimeout(cleanupCtx, 5*time.Second, 30*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
podList, err := kubeClient.CoreV1().Pods(vmiNamespace).List(ctx, metav1.ListOptions{
|
||||
LabelSelector: virtLauncherSelector,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
lPods := len(podList.Items)
|
||||
if lPods > 0 {
|
||||
klog.Infof("Waiting until all pods under default namespace are gone, %v remaining", lPods)
|
||||
klog.Infof("Waiting until all virt-launcher pods under %v namespace are gone, %v remaining", vmiNamespace, lPods)
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
}()
|
||||
|
||||
// Create N vmis and wait for the corresponding vm pods to be ready and running
|
||||
for i := 1; i <= vmiCount; i++ {
|
||||
vmi := virtualMachineInstance(i)
|
||||
_, err = kvClient.KubevirtV1().VirtualMachineInstances("default").Create(context.Background(), vmi, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create KubeVirt vmi: %v\n", err)
|
||||
if err := kubeClient.CoreV1().Namespaces().Delete(cleanupCtx, vmiNamespace, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
|
||||
klog.Infof("Unable to delete namespace %v: %v", vmiNamespace, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Wait until all VMIs have running pods
|
||||
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 300*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
return allVMIsHaveRunningPods(t, ctx, kubeClient, kvClient)
|
||||
}); err != nil {
|
||||
t.Fatalf("Error waiting for all vmi active pods to be running: %v", err)
|
||||
}
|
||||
|
||||
usedRunningPodNames := make(map[string]struct{})
|
||||
// vmiCount number of names is expected
|
||||
names := kVirtRunningPodNames(t, ctx, kubeClient)
|
||||
klog.Infof("vmi pods: %#v\n", names)
|
||||
if len(names) != vmiCount {
|
||||
t.Fatalf("Expected %v vmi pods, got %v instead", vmiCount, len(names))
|
||||
}
|
||||
for _, name := range names {
|
||||
usedRunningPodNames[name] = struct{}{}
|
||||
}
|
||||
|
||||
policy := podLifeTimePolicy()
|
||||
// Allow only a single eviction simultaneously
|
||||
policy.MaxNoOfPodsToEvictPerNamespace = utilptr.To[uint](1)
|
||||
// Deploy the descheduler with the configured policy
|
||||
deschedulerPolicyConfigMapObj, err := deschedulerPolicyConfigMap(policy)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
klog.Infof("Creating %q policy CM with RemovePodsHavingTooManyRestarts configured...", deschedulerPolicyConfigMapObj.Name)
|
||||
_, err = kubeClient.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Create(ctx, deschedulerPolicyConfigMapObj, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
defer func() {
|
||||
klog.Infof("Deleting %q CM...", deschedulerPolicyConfigMapObj.Name)
|
||||
err = kubeClient.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Delete(ctx, deschedulerPolicyConfigMapObj.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to delete %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
}()
|
||||
|
||||
deschedulerDeploymentObj := deschedulerDeployment("kube-system")
|
||||
// Set the descheduling interval to 10s
|
||||
deschedulerDeploymentObj.Spec.Template.Spec.Containers[0].Args = []string{"--policy-config-file", "/policy-dir/policy.yaml", "--descheduling-interval", "10s", "--v", "4", "--feature-gates", "EvictionsInBackground=true"}
|
||||
|
||||
deschedulerPodName := ""
|
||||
defer func() {
|
||||
if deschedulerPodName != "" {
|
||||
printPodLogs(ctx, t, kubeClient, deschedulerPodName)
|
||||
}
|
||||
|
||||
klog.Infof("Deleting %q deployment...", deschedulerDeploymentObj.Name)
|
||||
err = kubeClient.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(ctx, deschedulerDeploymentObj.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return
|
||||
}
|
||||
t.Fatalf("Unable to delete %q deployment: %v", deschedulerDeploymentObj.Name, err)
|
||||
}
|
||||
waitForPodsToDisappear(ctx, t, kubeClient, deschedulerDeploymentObj.Labels, deschedulerDeploymentObj.Namespace)
|
||||
}()
|
||||
|
||||
deschedulerPodName = createAndWaitForDeschedulerRunning(t, ctx, kubeClient, deschedulerDeploymentObj)
|
||||
|
||||
observeLiveMigration(t, ctx, kubeClient, usedRunningPodNames)
|
||||
|
||||
printPodLogs(ctx, t, kubeClient, deschedulerPodName)
|
||||
|
||||
klog.Infof("Deleting the current descheduler pod")
|
||||
err = kubeClient.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(ctx, deschedulerDeploymentObj.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Error deleting %q deployment: %v", deschedulerDeploymentObj.Name, err)
|
||||
}
|
||||
|
||||
remainingPods := make(map[string]struct{})
|
||||
for _, name := range kVirtRunningPodNames(t, ctx, kubeClient) {
|
||||
remainingPods[name] = struct{}{}
|
||||
}
|
||||
|
||||
klog.Infof("Configuring the descheduler policy %v for PodLifetime with no limits", deschedulerPolicyConfigMapObj.Name)
|
||||
policy.MaxNoOfPodsToEvictPerNamespace = nil
|
||||
updateDeschedulerPolicy(t, ctx, kubeClient, policy)
|
||||
|
||||
deschedulerDeploymentObj = deschedulerDeployment("kube-system")
|
||||
deschedulerDeploymentObj.Spec.Template.Spec.Containers[0].Args = []string{"--policy-config-file", "/policy-dir/policy.yaml", "--descheduling-interval", "100m", "--v", "4", "--feature-gates", "EvictionsInBackground=true"}
|
||||
deschedulerPodName = createAndWaitForDeschedulerRunning(t, ctx, kubeClient, deschedulerDeploymentObj)
|
||||
})
|
||||
}
|
||||
|
||||
func waitForVMIEvictionsWithNoLimits(t *testing.T, ctx context.Context, kubeClient clientset.Interface, vmiNamespace string, remainingPods map[string]struct{}) {
|
||||
t.Helper()
|
||||
klog.Infof("Waiting until all pods are evicted (no limit set)")
|
||||
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 120*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
names := kVirtRunningPodNames(t, ctx, kubeClient)
|
||||
names := kVirtRunningPodNames(t, ctx, kubeClient, vmiNamespace)
|
||||
for _, name := range names {
|
||||
if _, exists := remainingPods[name]; exists {
|
||||
klog.Infof("Waiting for %v to disappear", name)
|
||||
@@ -539,3 +482,107 @@ func TestLiveMigrationInBackground(t *testing.T) {
|
||||
t.Fatalf("Error waiting for %v new vmi active pods to be running: %v", vmiCount, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLiveMigrationInBackground(t *testing.T) {
|
||||
if *kubevirtVersionTag == "" {
|
||||
t.Fatal("--kubevirt-version-tag must be set for KubeVirt e2e tests")
|
||||
}
|
||||
|
||||
initPluginRegistry()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
kubeClient, err := client.CreateClient(componentbaseconfig.ClientConnectionConfiguration{Kubeconfig: os.Getenv("KUBECONFIG")}, "")
|
||||
if err != nil {
|
||||
t.Fatalf("Error during kubernetes client creation with %v", err)
|
||||
}
|
||||
|
||||
kvClient, err := createKubevirtClient()
|
||||
if err != nil {
|
||||
t.Fatalf("Error during kvClient creation with %v", err)
|
||||
}
|
||||
|
||||
waitForKubevirtReady(t, ctx, kvClient)
|
||||
|
||||
vmiNamespace := "e2e-livemigration"
|
||||
setupE2ELiveMigrationNamespace(t, ctx, kubeClient, kvClient, vmiNamespace)
|
||||
|
||||
for i := 1; i <= vmiCount; i++ {
|
||||
vmi := virtualMachineInstance(i, vmiNamespace)
|
||||
_, err = kvClient.KubevirtV1().VirtualMachineInstances(vmiNamespace).Create(context.Background(), vmi, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create KubeVirt vmi: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 300*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
return allVMIsHaveRunningPods(t, ctx, kubeClient, kvClient, vmiNamespace)
|
||||
}); err != nil {
|
||||
t.Fatalf("Error waiting for all vmi active pods to be running: %v", err)
|
||||
}
|
||||
|
||||
ensureVMIsLiveMigratable(t, ctx, kvClient, vmiNamespace)
|
||||
|
||||
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 300*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
return allVMIsHaveRunningPods(t, ctx, kubeClient, kvClient, vmiNamespace)
|
||||
}); err != nil {
|
||||
t.Fatalf("Error waiting for all vmi active pods to be running after recreate: %v", err)
|
||||
}
|
||||
|
||||
usedRunningPodNames := make(map[string]struct{})
|
||||
names := kVirtRunningPodNames(t, ctx, kubeClient, vmiNamespace)
|
||||
klog.Infof("vmi pods: %#v\n", names)
|
||||
if len(names) != vmiCount {
|
||||
t.Fatalf("Expected %v vmi pods, got %v instead", vmiCount, len(names))
|
||||
}
|
||||
for _, name := range names {
|
||||
usedRunningPodNames[name] = struct{}{}
|
||||
}
|
||||
|
||||
policy := podLifeTimePolicy(vmiNamespace)
|
||||
policy.MaxNoOfPodsToEvictPerNamespace = utilptr.To[uint](1)
|
||||
deschedulerPolicyConfigMapObj := createPolicyConfigMap(t, ctx, kubeClient, policy)
|
||||
|
||||
deschedulerDeploymentObj := deschedulerDeployment("kube-system")
|
||||
deschedulerDeploymentObj.Spec.Template.Spec.Containers[0].Args = []string{"--policy-config-file", "/policy-dir/policy.yaml", "--descheduling-interval", "10s", "--v", "4", "--feature-gates", "EvictionsInBackground=true"}
|
||||
|
||||
deschedulerPodName := ""
|
||||
t.Cleanup(func() {
|
||||
if deschedulerPodName != "" {
|
||||
printPodLogs(context.Background(), t, kubeClient, deschedulerPodName)
|
||||
}
|
||||
|
||||
klog.Infof("Deleting %q deployment...", deschedulerDeploymentObj.Name)
|
||||
if err := kubeClient.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(context.Background(), deschedulerDeploymentObj.Name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
|
||||
klog.Infof("Unable to delete %q deployment: %v", deschedulerDeploymentObj.Name, err)
|
||||
}
|
||||
waitForPodsToDisappear(context.Background(), t, kubeClient, deschedulerDeploymentObj.Labels, deschedulerDeploymentObj.Namespace)
|
||||
})
|
||||
|
||||
deschedulerPodName = createAndWaitForDeschedulerRunning(t, ctx, kubeClient, deschedulerDeploymentObj)
|
||||
|
||||
observeLiveMigration(t, ctx, kubeClient, vmiNamespace, usedRunningPodNames)
|
||||
|
||||
printPodLogs(ctx, t, kubeClient, deschedulerPodName)
|
||||
|
||||
klog.Infof("Deleting the current descheduler pod")
|
||||
err = kubeClient.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(ctx, deschedulerDeploymentObj.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Error deleting %q deployment: %v", deschedulerDeploymentObj.Name, err)
|
||||
}
|
||||
|
||||
remainingPods := make(map[string]struct{})
|
||||
for _, name := range kVirtRunningPodNames(t, ctx, kubeClient, vmiNamespace) {
|
||||
remainingPods[name] = struct{}{}
|
||||
}
|
||||
|
||||
klog.Infof("Configuring the descheduler policy %v for PodLifetime with no limits", deschedulerPolicyConfigMapObj.Name)
|
||||
policy.MaxNoOfPodsToEvictPerNamespace = nil
|
||||
updateDeschedulerPolicy(t, ctx, kubeClient, policy)
|
||||
|
||||
deschedulerDeploymentObj = deschedulerDeployment("kube-system")
|
||||
deschedulerDeploymentObj.Spec.Template.Spec.Containers[0].Args = []string{"--policy-config-file", "/policy-dir/policy.yaml", "--descheduling-interval", "100m", "--v", "4", "--feature-gates", "EvictionsInBackground=true"}
|
||||
deschedulerPodName = createAndWaitForDeschedulerRunning(t, ctx, kubeClient, deschedulerDeploymentObj)
|
||||
|
||||
waitForVMIEvictionsWithNoLimits(t, ctx, kubeClient, vmiNamespace, remainingPods)
|
||||
}
|
||||
|
||||
@@ -145,52 +145,10 @@ func TestFailedPods(t *testing.T) {
|
||||
Include: []string{testNamespace.Name},
|
||||
}
|
||||
|
||||
deschedulerPolicyConfigMapObj, err := deschedulerPolicyConfigMap(removeFailedPodsPolicy(tc.removeFailedPodsArgs, evictorArgs))
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
|
||||
t.Logf("Creating %q policy CM with RemoveDuplicates configured...", deschedulerPolicyConfigMapObj.Name)
|
||||
_, err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Create(ctx, deschedulerPolicyConfigMapObj, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
t.Logf("Deleting %q CM...", deschedulerPolicyConfigMapObj.Name)
|
||||
err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Delete(ctx, deschedulerPolicyConfigMapObj.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to delete %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
}()
|
||||
createPolicyConfigMap(t, ctx, clientSet, removeFailedPodsPolicy(tc.removeFailedPodsArgs, evictorArgs))
|
||||
|
||||
deschedulerDeploymentObj := deschedulerDeployment(testNamespace.Name)
|
||||
t.Logf("Creating descheduler deployment %v", deschedulerDeploymentObj.Name)
|
||||
_, err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Create(ctx, deschedulerDeploymentObj, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q deployment: %v", deschedulerDeploymentObj.Name, err)
|
||||
}
|
||||
|
||||
deschedulerPodName := ""
|
||||
defer func() {
|
||||
if deschedulerPodName != "" {
|
||||
printPodLogs(ctx, t, clientSet, deschedulerPodName)
|
||||
}
|
||||
|
||||
t.Logf("Deleting %q deployment...", deschedulerDeploymentObj.Name)
|
||||
err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(ctx, deschedulerDeploymentObj.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to delete %q deployment: %v", deschedulerDeploymentObj.Name, err)
|
||||
}
|
||||
|
||||
waitForPodsToDisappear(ctx, t, clientSet, deschedulerDeploymentObj.Labels, deschedulerDeploymentObj.Namespace)
|
||||
}()
|
||||
|
||||
t.Logf("Waiting for the descheduler pod running")
|
||||
deschedulerPods := waitForPodsRunning(ctx, t, clientSet, deschedulerDeploymentObj.Labels, 1, deschedulerDeploymentObj.Namespace)
|
||||
if len(deschedulerPods) != 0 {
|
||||
deschedulerPodName = deschedulerPods[0].Name
|
||||
}
|
||||
createDeschedulerDeploymentWithCleanup(t, ctx, clientSet, deschedulerDeploymentObj)
|
||||
|
||||
// Run RemoveDuplicates strategy
|
||||
var meetsExpectations bool
|
||||
|
||||
@@ -135,9 +135,9 @@ func TestLeaderElection(t *testing.T) {
|
||||
t.Logf("Removed kube-system/descheduler lease")
|
||||
|
||||
t.Log("Starting deschedulers")
|
||||
pod1Name, deploy1, cm1 := startDeschedulerServer(t, ctx, clientSet, ns1)
|
||||
pod1Name, deploy1, _ := startDeschedulerServer(t, ctx, clientSet, ns1)
|
||||
time.Sleep(1 * time.Second)
|
||||
pod2Name, deploy2, cm2 := startDeschedulerServer(t, ctx, clientSet, ns2)
|
||||
pod2Name, deploy2, _ := startDeschedulerServer(t, ctx, clientSet, ns2)
|
||||
defer func() {
|
||||
for _, podName := range []string{pod1Name, pod2Name} {
|
||||
printPodLogs(ctx, t, clientSet, podName)
|
||||
@@ -153,14 +153,6 @@ func TestLeaderElection(t *testing.T) {
|
||||
waitForPodsToDisappear(ctx, t, clientSet, deploy.Labels, deploy.Namespace)
|
||||
}
|
||||
|
||||
for _, cm := range []*v1.ConfigMap{cm1, cm2} {
|
||||
t.Logf("Deleting %q CM...", cm.Name)
|
||||
err = clientSet.CoreV1().ConfigMaps(cm.Namespace).Delete(ctx, cm.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to delete %q CM: %v", cm.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
clientSet.CoordinationV1().Leases("kube-system").Delete(ctx, "descheduler", metav1.DeleteOptions{})
|
||||
}()
|
||||
|
||||
@@ -223,21 +215,21 @@ func startDeschedulerServer(t *testing.T, ctx context.Context, clientSet clients
|
||||
EvictFailedBarePods: false,
|
||||
}
|
||||
deschedulerPolicyConfigMapObj, err := deschedulerPolicyConfigMap(podlifetimePolicy(podLifeTimeArgs, evictorArgs))
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
deschedulerPolicyConfigMapObj.Name = fmt.Sprintf("%s-%s", deschedulerPolicyConfigMapObj.Name, testName)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
|
||||
t.Logf("Creating %q policy CM with RemoveDuplicates configured...", deschedulerPolicyConfigMapObj.Name)
|
||||
_, err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Create(ctx, deschedulerPolicyConfigMapObj, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
createConfigMapWithCleanup(t, ctx, clientSet, deschedulerPolicyConfigMapObj)
|
||||
|
||||
deschedulerDeploymentObj := deschedulerDeployment(testName)
|
||||
deschedulerDeploymentObj.Name = fmt.Sprintf("%s-%s", deschedulerDeploymentObj.Name, testName)
|
||||
args := deschedulerDeploymentObj.Spec.Template.Spec.Containers[0].Args
|
||||
deschedulerDeploymentObj.Spec.Template.Spec.Containers[0].Args = append(args, "--leader-elect", "--leader-elect-retry-period", "1s")
|
||||
deschedulerDeploymentObj.Spec.Template.Spec.Containers[0].Args = []string{
|
||||
"--policy-config-file", "/policy-dir/policy.yaml",
|
||||
"--descheduling-interval", "3s",
|
||||
"--v", "4",
|
||||
"--leader-elect",
|
||||
"--leader-elect-retry-period", "1s",
|
||||
}
|
||||
deschedulerDeploymentObj.Spec.Template.Spec.Volumes = []v1.Volume{
|
||||
{
|
||||
Name: "policy-volume",
|
||||
|
||||
@@ -105,7 +105,10 @@ func TestLowNodeUtilizationKubernetesMetrics(t *testing.T) {
|
||||
if _, err := clientSet.CoreV1().Namespaces().Create(ctx, testNamespace, metav1.CreateOptions{}); err != nil {
|
||||
t.Fatalf("Unable to create ns %v: %v", testNamespace.Name, err)
|
||||
}
|
||||
defer clientSet.CoreV1().Namespaces().Delete(ctx, testNamespace.Name, metav1.DeleteOptions{})
|
||||
|
||||
t.Cleanup(func() {
|
||||
clientSet.CoreV1().Namespaces().Delete(ctx, testNamespace.Name, metav1.DeleteOptions{})
|
||||
})
|
||||
|
||||
t.Log("Creating duplicates pods")
|
||||
testLabel := map[string]string{"app": "test-lownodeutilization-kubernetes-metrics", "name": "test-lownodeutilization-kubernetes-metrics"}
|
||||
@@ -164,11 +167,11 @@ func TestLowNodeUtilizationKubernetesMetrics(t *testing.T) {
|
||||
expectedEvictedPodCount: 2,
|
||||
lowNodeUtilizationArgs: &nodeutilization.LowNodeUtilizationArgs{
|
||||
Thresholds: api.ResourceThresholds{
|
||||
v1.ResourceCPU: 30,
|
||||
v1.ResourceCPU: 8,
|
||||
v1.ResourcePods: 30,
|
||||
},
|
||||
TargetThresholds: api.ResourceThresholds{
|
||||
v1.ResourceCPU: 50,
|
||||
v1.ResourceCPU: 15,
|
||||
v1.ResourcePods: 50,
|
||||
},
|
||||
MetricsUtilization: &nodeutilization.MetricsUtilization{
|
||||
@@ -194,21 +197,22 @@ func TestLowNodeUtilizationKubernetesMetrics(t *testing.T) {
|
||||
}
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
|
||||
t.Cleanup(func() {
|
||||
clientSet.AppsV1().Deployments(deploymentObj.Namespace).Delete(ctx, deploymentObj.Name, metav1.DeleteOptions{})
|
||||
waitForPodsToDisappear(ctx, t, clientSet, deploymentObj.Labels, deploymentObj.Namespace)
|
||||
}()
|
||||
})
|
||||
waitForPodsRunning(ctx, t, clientSet, deploymentObj.Labels, tc.replicasNum, deploymentObj.Namespace)
|
||||
// wait until workerNodes[0].Name has the right actual cpu utilization and all the testing pods are running
|
||||
// and producing ~12 cores in total
|
||||
wait.PollUntilContextCancel(ctx, 5*time.Second, true, func(context.Context) (done bool, err error) {
|
||||
// and producing ~4 cores in total
|
||||
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 60*time.Second, true, func(ctx context.Context) (done bool, err error) {
|
||||
item, err := metricsClient.MetricsV1beta1().NodeMetricses().Get(ctx, workerNodes[0].Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Logf("unable to list nodemetricses: %v", err)
|
||||
return false, nil
|
||||
}
|
||||
t.Logf("Waiting for %q nodemetrics cpu utilization to get over 12, currently %v", workerNodes[0].Name, item.Usage.Cpu().Value())
|
||||
if item.Usage.Cpu().Value() < 12 {
|
||||
t.Logf("Waiting for %q nodemetrics cpu utilization to get over 3, currently %v", workerNodes[0].Name, item.Usage.Cpu().Value())
|
||||
if item.Usage.Cpu().Value() < 3 {
|
||||
return false, nil
|
||||
}
|
||||
totalCpu := resource.NewMilliQuantity(0, resource.DecimalSI)
|
||||
@@ -225,60 +229,20 @@ func TestLowNodeUtilizationKubernetesMetrics(t *testing.T) {
|
||||
totalCpu.Add(container.Usage[v1.ResourceCPU])
|
||||
}
|
||||
}
|
||||
// Value() will round up (e.g. 11.1 -> 12), which is still ok
|
||||
t.Logf("Waiting for totalCpu to get to 12 at least, got %v\n", totalCpu.Value())
|
||||
return totalCpu.Value() >= 12, nil
|
||||
})
|
||||
// Value() will round up (e.g. 3.1 -> 4), which is still ok
|
||||
t.Logf("Waiting for totalCpu to get to 3 at least, got %v\n", totalCpu.Value())
|
||||
return totalCpu.Value() >= 3, nil
|
||||
}); err != nil {
|
||||
t.Fatalf("Error waiting for node/pod metrics: %v", err)
|
||||
}
|
||||
|
||||
preRunNames := sets.NewString(getCurrentPodNames(ctx, clientSet, testNamespace.Name, t)...)
|
||||
|
||||
// Deploy the descheduler with the configured policy
|
||||
deschedulerPolicyConfigMapObj, err := deschedulerPolicyConfigMap(lowNodeUtilizationPolicy(tc.lowNodeUtilizationArgs, tc.evictorArgs, tc.metricsCollectorEnabled))
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
|
||||
t.Logf("Creating %q policy CM with LowNodeUtilization configured...", deschedulerPolicyConfigMapObj.Name)
|
||||
_, err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Create(ctx, deschedulerPolicyConfigMapObj, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
t.Logf("Deleting %q CM...", deschedulerPolicyConfigMapObj.Name)
|
||||
err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Delete(ctx, deschedulerPolicyConfigMapObj.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to delete %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
}()
|
||||
createPolicyConfigMap(t, ctx, clientSet, lowNodeUtilizationPolicy(tc.lowNodeUtilizationArgs, tc.evictorArgs, tc.metricsCollectorEnabled))
|
||||
|
||||
deschedulerDeploymentObj := deschedulerDeployment(testNamespace.Name)
|
||||
t.Logf("Creating descheduler deployment %v", deschedulerDeploymentObj.Name)
|
||||
_, err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Create(ctx, deschedulerDeploymentObj, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q deployment: %v", deschedulerDeploymentObj.Name, err)
|
||||
}
|
||||
|
||||
deschedulerPodName := ""
|
||||
defer func() {
|
||||
if deschedulerPodName != "" {
|
||||
printPodLogs(ctx, t, clientSet, deschedulerPodName)
|
||||
}
|
||||
|
||||
t.Logf("Deleting %q deployment...", deschedulerDeploymentObj.Name)
|
||||
err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(ctx, deschedulerDeploymentObj.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to delete %q deployment: %v", deschedulerDeploymentObj.Name, err)
|
||||
}
|
||||
|
||||
waitForPodsToDisappear(ctx, t, clientSet, deschedulerDeploymentObj.Labels, deschedulerDeploymentObj.Namespace)
|
||||
}()
|
||||
|
||||
t.Logf("Waiting for the descheduler pod running")
|
||||
deschedulerPods := waitForPodsRunning(ctx, t, clientSet, deschedulerDeploymentObj.Labels, 1, deschedulerDeploymentObj.Namespace)
|
||||
if len(deschedulerPods) != 0 {
|
||||
deschedulerPodName = deschedulerPods[0].Name
|
||||
}
|
||||
createDeschedulerDeploymentWithCleanup(t, ctx, clientSet, deschedulerDeploymentObj)
|
||||
|
||||
// Run LowNodeUtilization plugin
|
||||
var meetsExpectations bool
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
utilptr "k8s.io/utils/ptr"
|
||||
|
||||
deschedulerapi "sigs.k8s.io/descheduler/pkg/api"
|
||||
"sigs.k8s.io/descheduler/pkg/framework/plugins/defaultevictor"
|
||||
"sigs.k8s.io/descheduler/pkg/framework/plugins/podlifetime"
|
||||
)
|
||||
|
||||
func TestPodLifeTime_FailedPods(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
clientSet, _, nodeLister, _ := initializeClient(ctx, t)
|
||||
|
||||
t.Log("Creating testing namespace")
|
||||
testNamespace := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "e2e-" + strings.ReplaceAll(strings.ToLower(t.Name()), "_", "-")}}
|
||||
if _, err := clientSet.CoreV1().Namespaces().Create(ctx, testNamespace, metav1.CreateOptions{}); err != nil {
|
||||
t.Fatalf("Unable to create ns %v", testNamespace.Name)
|
||||
}
|
||||
defer clientSet.CoreV1().Namespaces().Delete(ctx, testNamespace.Name, metav1.DeleteOptions{})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
expectedEvictedPodCount int
|
||||
args *podlifetime.PodLifeTimeArgs
|
||||
}{
|
||||
{
|
||||
name: "test-transition-failed-pods-default",
|
||||
expectedEvictedPodCount: 1,
|
||||
args: &podlifetime.PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodFailed)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test-transition-failed-pods-exclude-job",
|
||||
expectedEvictedPodCount: 0,
|
||||
args: &podlifetime.PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodFailed)},
|
||||
OwnerKinds: &podlifetime.OwnerKinds{Exclude: []string{"Job"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
job := initTransitionTestJob(tc.name, testNamespace.Name)
|
||||
t.Logf("Creating job %s in %s namespace", job.Name, testNamespace.Name)
|
||||
jobClient := clientSet.BatchV1().Jobs(testNamespace.Name)
|
||||
if _, err := jobClient.Create(ctx, job, metav1.CreateOptions{}); err != nil {
|
||||
t.Fatalf("Error creating Job %s: %v", tc.name, err)
|
||||
}
|
||||
deletePropagationPolicy := metav1.DeletePropagationForeground
|
||||
defer func() {
|
||||
jobClient.Delete(ctx, job.Name, metav1.DeleteOptions{PropagationPolicy: &deletePropagationPolicy})
|
||||
waitForPodsToDisappear(ctx, t, clientSet, job.Labels, testNamespace.Name)
|
||||
}()
|
||||
waitForTransitionJobPodPhase(ctx, t, clientSet, job, v1.PodFailed)
|
||||
|
||||
preRunNames := sets.NewString(getCurrentPodNames(ctx, clientSet, testNamespace.Name, t)...)
|
||||
|
||||
tc.args.Namespaces = &deschedulerapi.Namespaces{
|
||||
Include: []string{testNamespace.Name},
|
||||
}
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeLister, tc.args,
|
||||
defaultevictor.DefaultEvictorArgs{EvictLocalStoragePods: true},
|
||||
nil,
|
||||
)
|
||||
|
||||
var meetsExpectations bool
|
||||
var actualEvictedPodCount int
|
||||
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
currentRunNames := sets.NewString(getCurrentPodNames(ctx, clientSet, testNamespace.Name, t)...)
|
||||
actualEvictedPod := preRunNames.Difference(currentRunNames)
|
||||
actualEvictedPodCount = actualEvictedPod.Len()
|
||||
t.Logf("preRunNames: %v, currentRunNames: %v, actualEvictedPodCount: %v\n", preRunNames.List(), currentRunNames.List(), actualEvictedPodCount)
|
||||
if actualEvictedPodCount != tc.expectedEvictedPodCount {
|
||||
t.Logf("Expecting %v number of pods evicted, got %v instead", tc.expectedEvictedPodCount, actualEvictedPodCount)
|
||||
return false, nil
|
||||
}
|
||||
meetsExpectations = true
|
||||
return true, nil
|
||||
}); err != nil {
|
||||
t.Errorf("Error waiting for expected eviction count: %v", err)
|
||||
}
|
||||
|
||||
if !meetsExpectations {
|
||||
t.Errorf("Unexpected number of pods have been evicted, got %v, expected %v", actualEvictedPodCount, tc.expectedEvictedPodCount)
|
||||
} else {
|
||||
t.Logf("Total of %d Pods were evicted for %s", actualEvictedPodCount, tc.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPodLifeTime_SucceededPods(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
clientSet, _, nodeLister, _ := initializeClient(ctx, t)
|
||||
|
||||
t.Log("Creating testing namespace")
|
||||
testNamespace := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "e2e-" + strings.ReplaceAll(strings.ToLower(t.Name()), "_", "-")}}
|
||||
if _, err := clientSet.CoreV1().Namespaces().Create(ctx, testNamespace, metav1.CreateOptions{}); err != nil {
|
||||
t.Fatalf("Unable to create ns %v", testNamespace.Name)
|
||||
}
|
||||
defer clientSet.CoreV1().Namespaces().Delete(ctx, testNamespace.Name, metav1.DeleteOptions{})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
expectedEvictedPodCount int
|
||||
args *podlifetime.PodLifeTimeArgs
|
||||
}{
|
||||
{
|
||||
name: "test-transition-succeeded-pods",
|
||||
expectedEvictedPodCount: 1,
|
||||
args: &podlifetime.PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodSucceeded)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test-transition-succeeded-condition",
|
||||
expectedEvictedPodCount: 1,
|
||||
args: &podlifetime.PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodSucceeded)},
|
||||
Conditions: []podlifetime.PodConditionFilter{
|
||||
{Reason: "PodCompleted", Status: "True"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test-transition-succeeded-condition-unmatched",
|
||||
expectedEvictedPodCount: 0,
|
||||
args: &podlifetime.PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodSucceeded)},
|
||||
Conditions: []podlifetime.PodConditionFilter{
|
||||
{Reason: "ReasonDoesNotMatch"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
job := initTransitionSucceededJob(tc.name, testNamespace.Name)
|
||||
t.Logf("Creating job %s in %s namespace", job.Name, testNamespace.Name)
|
||||
jobClient := clientSet.BatchV1().Jobs(testNamespace.Name)
|
||||
if _, err := jobClient.Create(ctx, job, metav1.CreateOptions{}); err != nil {
|
||||
t.Fatalf("Error creating Job %s: %v", tc.name, err)
|
||||
}
|
||||
deletePropagationPolicy := metav1.DeletePropagationForeground
|
||||
defer func() {
|
||||
jobClient.Delete(ctx, job.Name, metav1.DeleteOptions{PropagationPolicy: &deletePropagationPolicy})
|
||||
waitForPodsToDisappear(ctx, t, clientSet, job.Labels, testNamespace.Name)
|
||||
}()
|
||||
waitForTransitionJobPodPhase(ctx, t, clientSet, job, v1.PodSucceeded)
|
||||
|
||||
preRunNames := sets.NewString(getCurrentPodNames(ctx, clientSet, testNamespace.Name, t)...)
|
||||
|
||||
tc.args.Namespaces = &deschedulerapi.Namespaces{
|
||||
Include: []string{testNamespace.Name},
|
||||
}
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeLister, tc.args,
|
||||
defaultevictor.DefaultEvictorArgs{EvictLocalStoragePods: true},
|
||||
nil,
|
||||
)
|
||||
|
||||
var meetsExpectations bool
|
||||
var actualEvictedPodCount int
|
||||
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
currentRunNames := sets.NewString(getCurrentPodNames(ctx, clientSet, testNamespace.Name, t)...)
|
||||
actualEvictedPod := preRunNames.Difference(currentRunNames)
|
||||
actualEvictedPodCount = actualEvictedPod.Len()
|
||||
t.Logf("preRunNames: %v, currentRunNames: %v, actualEvictedPodCount: %v\n", preRunNames.List(), currentRunNames.List(), actualEvictedPodCount)
|
||||
if actualEvictedPodCount != tc.expectedEvictedPodCount {
|
||||
t.Logf("Expecting %v number of pods evicted, got %v instead", tc.expectedEvictedPodCount, actualEvictedPodCount)
|
||||
return false, nil
|
||||
}
|
||||
meetsExpectations = true
|
||||
return true, nil
|
||||
}); err != nil {
|
||||
t.Errorf("Error waiting for expected eviction count: %v", err)
|
||||
}
|
||||
|
||||
if !meetsExpectations {
|
||||
t.Errorf("Unexpected number of pods have been evicted, got %v, expected %v", actualEvictedPodCount, tc.expectedEvictedPodCount)
|
||||
} else {
|
||||
t.Logf("Total of %d Pods were evicted for %s", actualEvictedPodCount, tc.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func initTransitionTestJob(name, namespace string) *batchv1.Job {
|
||||
podSpec := makePodSpec("", nil)
|
||||
podSpec.Containers[0].Command = []string{"/bin/false"}
|
||||
podSpec.RestartPolicy = v1.RestartPolicyNever
|
||||
labelsSet := labels.Set{"test": name, "name": name}
|
||||
return &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: labelsSet,
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: podSpec,
|
||||
ObjectMeta: metav1.ObjectMeta{Labels: labelsSet},
|
||||
},
|
||||
BackoffLimit: utilptr.To[int32](0),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func initTransitionSucceededJob(name, namespace string) *batchv1.Job {
|
||||
podSpec := makePodSpec("", nil)
|
||||
podSpec.Containers[0].Image = "registry.k8s.io/e2e-test-images/agnhost:2.43"
|
||||
podSpec.Containers[0].Command = []string{"/bin/sh", "-c", "exit 0"}
|
||||
podSpec.RestartPolicy = v1.RestartPolicyNever
|
||||
labelsSet := labels.Set{"test": name, "name": name}
|
||||
return &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: labelsSet,
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: podSpec,
|
||||
ObjectMeta: metav1.ObjectMeta{Labels: labelsSet},
|
||||
},
|
||||
BackoffLimit: utilptr.To[int32](0),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func waitForTransitionJobPodPhase(ctx context.Context, t *testing.T, clientSet clientset.Interface, job *batchv1.Job, phase v1.PodPhase) {
|
||||
podClient := clientSet.CoreV1().Pods(job.Namespace)
|
||||
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 30*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
t.Log(labels.FormatLabels(job.Labels))
|
||||
if podList, err := podClient.List(ctx, metav1.ListOptions{LabelSelector: labels.FormatLabels(job.Labels)}); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
if len(podList.Items) == 0 {
|
||||
t.Logf("Job controller has not created Pod for job %s yet", job.Name)
|
||||
return false, nil
|
||||
}
|
||||
for _, pod := range podList.Items {
|
||||
if pod.Status.Phase != phase {
|
||||
t.Logf("Pod %v not in %s phase yet, is %v instead", pod.Name, phase, pod.Status.Phase)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
t.Logf("Job %v Pod is in %s phase now", job.Name, phase)
|
||||
return true, nil
|
||||
}
|
||||
}); err != nil {
|
||||
t.Fatalf("Error waiting for pods in %s phase: %v", phase, err)
|
||||
}
|
||||
}
|
||||
@@ -289,21 +289,7 @@ func TestProtectPodsWithPVC(t *testing.T) {
|
||||
t.Fatalf("Error creating %q CM: %v", policycm.Name, err)
|
||||
}
|
||||
|
||||
t.Logf("creating %q policy CM with PodsWithPVC protection enabled...", policycm.Name)
|
||||
if _, err = cli.CoreV1().ConfigMaps(policycm.Namespace).Create(
|
||||
ctx, policycm, metav1.CreateOptions{},
|
||||
); err != nil {
|
||||
t.Fatalf("error creating %q CM: %v", policycm.Name, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
t.Logf("deleting %q CM...", policycm.Name)
|
||||
if err := cli.CoreV1().ConfigMaps(policycm.Namespace).Delete(
|
||||
ctx, policycm.Name, metav1.DeleteOptions{},
|
||||
); err != nil {
|
||||
t.Fatalf("unable to delete %q CM: %v", policycm.Name, err)
|
||||
}
|
||||
}()
|
||||
createConfigMapWithCleanup(t, ctx, cli, policycm)
|
||||
|
||||
desdep := deschedulerDeployment(namespace.Name)
|
||||
t.Logf("creating descheduler deployment %v", desdep.Name)
|
||||
|
||||
+165
-54
@@ -31,6 +31,7 @@ import (
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
schedulingv1 "k8s.io/api/scheduling/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
@@ -67,11 +68,16 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
deschedulerImage = flag.String("descheduler-image", "", "descheduler image to set in the pod spec")
|
||||
podRunAsUserId = flag.Int64("pod-run-as-user-id", 0, ".spec.securityContext.runAsUser setting, not set if 0")
|
||||
podRunAsGroupId = flag.Int64("pod-run-as-group-id", 0, ".spec.securityContext.runAsGroup setting, not set if 0")
|
||||
deschedulerImage = flag.String("descheduler-image", "", "descheduler image to set in the pod spec")
|
||||
kubevirtVersionTag = flag.String("kubevirt-version-tag", "", "KubeVirt release tag for container disk images in KubeVirt e2e tests (e.g. v1.9.0)")
|
||||
podRunAsUserId = flag.Int64("pod-run-as-user-id", 0, ".spec.securityContext.runAsUser setting, not set if 0")
|
||||
podRunAsGroupId = flag.Int64("pod-run-as-group-id", 0, ".spec.securityContext.runAsGroup setting, not set if 0")
|
||||
)
|
||||
|
||||
func kubevirtCirrosContainerDiskImage() string {
|
||||
return fmt.Sprintf("quay.io/kubevirt/cirros-container-disk-demo:%s", *kubevirtVersionTag)
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
|
||||
@@ -84,7 +90,12 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
func isClientRateLimiterError(err error) bool {
|
||||
return strings.Contains(err.Error(), "client rate limiter")
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(err.Error(), "client rate limiter") &&
|
||||
!strings.Contains(err.Error(), "context deadline exceeded") &&
|
||||
!strings.Contains(err.Error(), "context canceled")
|
||||
}
|
||||
|
||||
func initFeatureGates() featuregate.FeatureGate {
|
||||
@@ -112,6 +123,78 @@ func deschedulerPolicyConfigMap(policy *deschedulerapiv1alpha2.DeschedulerPolicy
|
||||
return cm, nil
|
||||
}
|
||||
|
||||
// createPolicyConfigMap generates, creates (or recreates if already existing), and registers cleanup for a descheduler policy ConfigMap.
|
||||
func createPolicyConfigMap(t *testing.T, ctx context.Context, kubeClient clientset.Interface, policy *deschedulerapiv1alpha2.DeschedulerPolicy) *v1.ConfigMap {
|
||||
t.Helper()
|
||||
cm, err := deschedulerPolicyConfigMap(policy)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating policy CM object: %v", err)
|
||||
}
|
||||
return createConfigMapWithCleanup(t, ctx, kubeClient, cm)
|
||||
}
|
||||
|
||||
// createConfigMapWithCleanup creates (or recreates if already existing) a ConfigMap and registers a t.Cleanup callback to delete it.
|
||||
func createConfigMapWithCleanup(t *testing.T, ctx context.Context, kubeClient clientset.Interface, cm *v1.ConfigMap) *v1.ConfigMap {
|
||||
t.Helper()
|
||||
t.Logf("Creating %q policy CM...", cm.Name)
|
||||
_, err := kubeClient.CoreV1().ConfigMaps(cm.Namespace).Create(ctx, cm, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
if apierrors.IsAlreadyExists(err) {
|
||||
_ = kubeClient.CoreV1().ConfigMaps(cm.Namespace).Delete(ctx, cm.Name, metav1.DeleteOptions{})
|
||||
_, err = kubeClient.CoreV1().ConfigMaps(cm.Namespace).Create(ctx, cm, metav1.CreateOptions{})
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q CM: %v", cm.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
t.Logf("Deleting %q CM...", cm.Name)
|
||||
if err := kubeClient.CoreV1().ConfigMaps(cm.Namespace).Delete(context.Background(), cm.Name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
|
||||
t.Logf("Unable to delete %q CM: %v", cm.Name, err)
|
||||
}
|
||||
})
|
||||
|
||||
return cm
|
||||
}
|
||||
|
||||
// createDeschedulerDeploymentWithCleanup creates (or recreates if already existing) a descheduler deployment and registers a t.Cleanup callback.
|
||||
func createDeschedulerDeploymentWithCleanup(t *testing.T, ctx context.Context, kubeClient clientset.Interface, deployment *appsv1.Deployment) string {
|
||||
t.Helper()
|
||||
t.Logf("Creating descheduler deployment %v", deployment.Name)
|
||||
_, err := kubeClient.AppsV1().Deployments(deployment.Namespace).Create(ctx, deployment, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
if apierrors.IsAlreadyExists(err) {
|
||||
_ = kubeClient.AppsV1().Deployments(deployment.Namespace).Delete(ctx, deployment.Name, metav1.DeleteOptions{})
|
||||
_, err = kubeClient.AppsV1().Deployments(deployment.Namespace).Create(ctx, deployment, metav1.CreateOptions{})
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q deployment: %v", deployment.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
deschedulerPodName := ""
|
||||
t.Cleanup(func() {
|
||||
if deschedulerPodName != "" {
|
||||
printPodLogs(context.Background(), t, kubeClient, deschedulerPodName)
|
||||
}
|
||||
|
||||
t.Logf("Deleting %q deployment...", deployment.Name)
|
||||
if err := kubeClient.AppsV1().Deployments(deployment.Namespace).Delete(context.Background(), deployment.Name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
|
||||
t.Logf("Unable to delete %q deployment: %v", deployment.Name, err)
|
||||
}
|
||||
|
||||
waitForPodsToDisappear(context.Background(), t, kubeClient, deployment.Labels, deployment.Namespace)
|
||||
})
|
||||
|
||||
t.Logf("Waiting for the descheduler pod running")
|
||||
deschedulerPods := waitForPodsRunning(ctx, t, kubeClient, deployment.Labels, 1, deployment.Namespace)
|
||||
if len(deschedulerPods) != 0 {
|
||||
deschedulerPodName = deschedulerPods[0].Name
|
||||
}
|
||||
return deschedulerPodName
|
||||
}
|
||||
|
||||
func deschedulerDeployment(testName string) *appsv1.Deployment {
|
||||
deploymentObject := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -421,14 +504,12 @@ func runPodLifetimePlugin(
|
||||
t *testing.T,
|
||||
clientset clientset.Interface,
|
||||
nodeLister listersv1.NodeLister,
|
||||
namespaces *deschedulerapi.Namespaces,
|
||||
priorityClass string,
|
||||
priority *int32,
|
||||
evictCritical bool,
|
||||
evictDaemonSet bool,
|
||||
maxPodsToEvictPerNamespace *uint,
|
||||
labelSelector *metav1.LabelSelector,
|
||||
args *podlifetime.PodLifeTimeArgs,
|
||||
evictorArgs defaultevictor.DefaultEvictorArgs,
|
||||
evictionOpts *evictions.Options,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
evictionPolicyGroupVersion, err := eutils.SupportEviction(clientset)
|
||||
if err != nil || len(evictionPolicyGroupVersion) == 0 {
|
||||
t.Fatalf("%v", err)
|
||||
@@ -439,42 +520,18 @@ func runPodLifetimePlugin(
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
var thresholdPriority int32
|
||||
if priority != nil {
|
||||
thresholdPriority = *priority
|
||||
if evictionOpts == nil {
|
||||
evictionOpts = evictions.NewOptions().WithPolicyGroupVersion(evictionPolicyGroupVersion)
|
||||
} else {
|
||||
thresholdPriority, err = utils.GetPriorityFromPriorityClass(ctx, clientset, priorityClass)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get threshold priority from plugin arg params")
|
||||
}
|
||||
evictionOpts = evictionOpts.WithPolicyGroupVersion(evictionPolicyGroupVersion)
|
||||
}
|
||||
|
||||
handle, _, err := frameworktesting.InitFrameworkHandle(
|
||||
ctx,
|
||||
clientset,
|
||||
evictions.NewOptions().
|
||||
WithPolicyGroupVersion(evictionPolicyGroupVersion).
|
||||
WithMaxPodsToEvictPerNamespace(maxPodsToEvictPerNamespace),
|
||||
defaultevictor.DefaultEvictorArgs{
|
||||
EvictSystemCriticalPods: evictCritical,
|
||||
EvictDaemonSetPods: evictDaemonSet,
|
||||
PriorityThreshold: &deschedulerapi.PriorityThreshold{
|
||||
Value: &thresholdPriority,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
handle, _, err := frameworktesting.InitFrameworkHandle(ctx, clientset, evictionOpts, evictorArgs, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to initialize a framework handle: %v", err)
|
||||
}
|
||||
|
||||
maxPodLifeTimeSeconds := uint(1)
|
||||
|
||||
plugin, err := podlifetime.New(ctx, &podlifetime.PodLifeTimeArgs{
|
||||
MaxPodLifeTimeSeconds: &maxPodLifeTimeSeconds,
|
||||
LabelSelector: labelSelector,
|
||||
Namespaces: namespaces,
|
||||
}, handle)
|
||||
plugin, err := podlifetime.New(ctx, args, handle)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to initialize the plugin: %v", err)
|
||||
}
|
||||
@@ -707,9 +764,14 @@ func TestNamespaceConstraintsInclude(t *testing.T) {
|
||||
t.Logf("Existing pods: %v", initialPodNames)
|
||||
|
||||
t.Logf("run the plugin to delete pods from %v namespace", rc.Namespace)
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer, &deschedulerapi.Namespaces{
|
||||
Include: []string{rc.Namespace},
|
||||
}, "", nil, false, false, nil, nil)
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer,
|
||||
&podlifetime.PodLifeTimeArgs{
|
||||
MaxPodLifeTimeSeconds: utilptr.To[uint](1),
|
||||
Namespaces: &deschedulerapi.Namespaces{Include: []string{rc.Namespace}},
|
||||
},
|
||||
defaultevictor.DefaultEvictorArgs{},
|
||||
nil,
|
||||
)
|
||||
|
||||
// All pods are supposed to be deleted, wait until all the old pods are deleted
|
||||
if err := wait.PollUntilContextTimeout(ctx, time.Second, 20*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
@@ -777,9 +839,14 @@ func TestNamespaceConstraintsExclude(t *testing.T) {
|
||||
t.Logf("Existing pods: %v", initialPodNames)
|
||||
|
||||
t.Logf("run the plugin to delete pods from namespaces except the %v namespace", rc.Namespace)
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer, &deschedulerapi.Namespaces{
|
||||
Exclude: []string{rc.Namespace},
|
||||
}, "", nil, false, false, nil, nil)
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer,
|
||||
&podlifetime.PodLifeTimeArgs{
|
||||
MaxPodLifeTimeSeconds: utilptr.To[uint](1),
|
||||
Namespaces: &deschedulerapi.Namespaces{Exclude: []string{rc.Namespace}},
|
||||
},
|
||||
defaultevictor.DefaultEvictorArgs{},
|
||||
nil,
|
||||
)
|
||||
|
||||
t.Logf("Waiting 10s")
|
||||
time.Sleep(10 * time.Second)
|
||||
@@ -890,11 +957,24 @@ func testEvictSystemCritical(t *testing.T, isPriorityClass bool) {
|
||||
sort.Strings(initialPodNames)
|
||||
t.Logf("Existing pods: %v", initialPodNames)
|
||||
|
||||
var thresholdPriority int32
|
||||
if isPriorityClass {
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer, nil, highPriorityClass.Name, nil, true, false, nil, nil)
|
||||
resolved, err := utils.GetPriorityFromPriorityClass(ctx, clientSet, highPriorityClass.Name)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get priority from priority class: %v", err)
|
||||
}
|
||||
thresholdPriority = resolved
|
||||
} else {
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer, nil, "", &highPriority, true, false, nil, nil)
|
||||
thresholdPriority = highPriority
|
||||
}
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer,
|
||||
&podlifetime.PodLifeTimeArgs{MaxPodLifeTimeSeconds: utilptr.To[uint](1)},
|
||||
defaultevictor.DefaultEvictorArgs{
|
||||
EvictSystemCriticalPods: true,
|
||||
PriorityThreshold: &deschedulerapi.PriorityThreshold{Value: &thresholdPriority},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
// All pods are supposed to be deleted, wait until all pods in the test namespace are terminating
|
||||
t.Logf("All pods in the test namespace, no matter their priority (including system-node-critical and system-cluster-critical), will be deleted")
|
||||
@@ -961,7 +1041,11 @@ func testEvictDaemonSetPod(t *testing.T, isDaemonSet bool) {
|
||||
sort.Strings(initialPodNames)
|
||||
t.Logf("Existing pods: %v", initialPodNames)
|
||||
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer, nil, "", nil, false, isDaemonSet, nil, nil)
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer,
|
||||
&podlifetime.PodLifeTimeArgs{MaxPodLifeTimeSeconds: utilptr.To[uint](1)},
|
||||
defaultevictor.DefaultEvictorArgs{EvictDaemonSetPods: isDaemonSet},
|
||||
nil,
|
||||
)
|
||||
|
||||
// All pods are supposed to be deleted, wait until all pods in the test namespace are terminating
|
||||
t.Logf("All daemonset pods in the test namespace, will be deleted")
|
||||
@@ -1074,13 +1158,25 @@ func testPriority(t *testing.T, isPriorityClass bool) {
|
||||
sort.Strings(expectEvictPodNames)
|
||||
t.Logf("Pods not expected to be evicted: %v, pods expected to be evicted: %v", expectReservePodNames, expectEvictPodNames)
|
||||
|
||||
var thresholdPriority int32
|
||||
if isPriorityClass {
|
||||
t.Logf("run the plugin to delete pods with priority lower than priority class %s", highPriorityClass.Name)
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer, nil, highPriorityClass.Name, nil, false, false, nil, nil)
|
||||
resolved, err := utils.GetPriorityFromPriorityClass(ctx, clientSet, highPriorityClass.Name)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get priority from priority class: %v", err)
|
||||
}
|
||||
thresholdPriority = resolved
|
||||
} else {
|
||||
t.Logf("run the plugin to delete pods with priority lower than %d", highPriority)
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer, nil, "", &highPriority, false, false, nil, nil)
|
||||
thresholdPriority = highPriority
|
||||
}
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer,
|
||||
&podlifetime.PodLifeTimeArgs{MaxPodLifeTimeSeconds: utilptr.To[uint](1)},
|
||||
defaultevictor.DefaultEvictorArgs{
|
||||
PriorityThreshold: &deschedulerapi.PriorityThreshold{Value: &thresholdPriority},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
t.Logf("Waiting 10s")
|
||||
time.Sleep(10 * time.Second)
|
||||
@@ -1182,7 +1278,14 @@ func TestPodLabelSelector(t *testing.T) {
|
||||
t.Logf("Pods not expected to be evicted: %v, pods expected to be evicted: %v", expectReservePodNames, expectEvictPodNames)
|
||||
|
||||
t.Logf("run the plugin to delete pods with label test:podlifetime-evict")
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer, nil, "", nil, false, false, nil, &metav1.LabelSelector{MatchLabels: map[string]string{"test": "podlifetime-evict"}})
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer,
|
||||
&podlifetime.PodLifeTimeArgs{
|
||||
MaxPodLifeTimeSeconds: utilptr.To[uint](1),
|
||||
LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"test": "podlifetime-evict"}},
|
||||
},
|
||||
defaultevictor.DefaultEvictorArgs{},
|
||||
nil,
|
||||
)
|
||||
|
||||
t.Logf("Waiting 10s")
|
||||
time.Sleep(10 * time.Second)
|
||||
@@ -1281,7 +1384,11 @@ func TestEvictAnnotation(t *testing.T) {
|
||||
t.Logf("Existing pods: %v", initialPodNames)
|
||||
|
||||
t.Log("Running PodLifetime plugin")
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeLister, nil, "", nil, false, false, nil, nil)
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeLister,
|
||||
&podlifetime.PodLifeTimeArgs{MaxPodLifeTimeSeconds: utilptr.To[uint](1)},
|
||||
defaultevictor.DefaultEvictorArgs{},
|
||||
nil,
|
||||
)
|
||||
|
||||
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, time.Minute, true, func(ctx context.Context) (bool, error) {
|
||||
podList, err = clientSet.CoreV1().Pods(rc.Namespace).List(ctx, metav1.ListOptions{LabelSelector: labels.SelectorFromSet(rc.Spec.Template.Labels).String()})
|
||||
@@ -1346,7 +1453,11 @@ func TestPodLifeTimeOldestEvicted(t *testing.T) {
|
||||
|
||||
t.Log("Running PodLifetime plugin with maxPodsToEvictPerNamespace=1 to ensure only the oldest pod is evicted")
|
||||
var maxPodsToEvictPerNamespace uint = 1
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeLister, nil, "", nil, false, false, &maxPodsToEvictPerNamespace, nil)
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeLister,
|
||||
&podlifetime.PodLifeTimeArgs{MaxPodLifeTimeSeconds: utilptr.To[uint](1)},
|
||||
defaultevictor.DefaultEvictorArgs{},
|
||||
evictions.NewOptions().WithMaxPodsToEvictPerNamespace(&maxPodsToEvictPerNamespace),
|
||||
)
|
||||
t.Log("Finished PodLifetime plugin")
|
||||
|
||||
t.Logf("Wait for terminating pod to disappear")
|
||||
|
||||
@@ -153,51 +153,10 @@ func TestTooManyRestarts(t *testing.T) {
|
||||
rs.DefaultFeatureGates = initFeatureGates()
|
||||
|
||||
preRunNames := sets.NewString(getCurrentPodNames(ctx, clientSet, testNamespace.Name, t)...)
|
||||
// Deploy the descheduler with the configured policy
|
||||
deschedulerPolicyConfigMapObj, err := deschedulerPolicyConfigMap(tc.policy)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
t.Logf("Creating %q policy CM with RemovePodsHavingTooManyRestarts configured...", deschedulerPolicyConfigMapObj.Name)
|
||||
_, err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Create(ctx, deschedulerPolicyConfigMapObj, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
t.Logf("Deleting %q CM...", deschedulerPolicyConfigMapObj.Name)
|
||||
err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Delete(ctx, deschedulerPolicyConfigMapObj.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to delete %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
}()
|
||||
createPolicyConfigMap(t, ctx, clientSet, tc.policy)
|
||||
|
||||
deschedulerDeploymentObj := deschedulerDeployment(testNamespace.Name)
|
||||
t.Logf("Creating descheduler deployment %v", deschedulerDeploymentObj.Name)
|
||||
_, err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Create(ctx, deschedulerDeploymentObj, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q deployment: %v", deschedulerDeploymentObj.Name, err)
|
||||
}
|
||||
|
||||
deschedulerPodName := ""
|
||||
defer func() {
|
||||
if deschedulerPodName != "" {
|
||||
printPodLogs(ctx, t, clientSet, deschedulerPodName)
|
||||
}
|
||||
|
||||
t.Logf("Deleting %q deployment...", deschedulerDeploymentObj.Name)
|
||||
err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(ctx, deschedulerDeploymentObj.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to delete %q deployment: %v", deschedulerDeploymentObj.Name, err)
|
||||
}
|
||||
waitForPodsToDisappear(ctx, t, clientSet, deschedulerDeploymentObj.Labels, deschedulerDeploymentObj.Namespace)
|
||||
}()
|
||||
|
||||
t.Logf("Waiting for the descheduler pod running")
|
||||
deschedulerPods := waitForPodsRunning(ctx, t, clientSet, deschedulerDeploymentObj.Labels, 1, deschedulerDeploymentObj.Namespace)
|
||||
if len(deschedulerPods) != 0 {
|
||||
deschedulerPodName = deschedulerPods[0].Name
|
||||
}
|
||||
createDeschedulerDeploymentWithCleanup(t, ctx, clientSet, deschedulerDeploymentObj)
|
||||
// Run RemovePodsHavingTooManyRestarts strategy
|
||||
if err := wait.PollUntilContextTimeout(ctx, 1*time.Second, 50*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
currentRunNames := sets.NewString(getCurrentPodNames(ctx, clientSet, testNamespace.Name, t)...)
|
||||
|
||||
@@ -219,50 +219,9 @@ func TestTopologySpreadConstraint(t *testing.T) {
|
||||
Include: []string{testNamespace.Name},
|
||||
},
|
||||
}
|
||||
deschedulerPolicyConfigMapObj, err := deschedulerPolicyConfigMap(topologySpreadConstraintPolicy(constraintArgs, evictorArgs))
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
|
||||
t.Logf("Creating %q policy CM with RemovePodsHavingTooManyRestarts configured...", deschedulerPolicyConfigMapObj.Name)
|
||||
_, err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Create(ctx, deschedulerPolicyConfigMapObj, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
t.Logf("Deleting %q CM...", deschedulerPolicyConfigMapObj.Name)
|
||||
err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Delete(ctx, deschedulerPolicyConfigMapObj.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to delete %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
|
||||
}
|
||||
}()
|
||||
createPolicyConfigMap(t, ctx, clientSet, topologySpreadConstraintPolicy(constraintArgs, evictorArgs))
|
||||
deschedulerDeploymentObj := deschedulerDeployment(testNamespace.Name)
|
||||
t.Logf("Creating descheduler deployment %v", deschedulerDeploymentObj.Name)
|
||||
_, err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Create(ctx, deschedulerDeploymentObj, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating %q deployment: %v", deschedulerDeploymentObj.Name, err)
|
||||
}
|
||||
|
||||
deschedulerPodName := ""
|
||||
defer func() {
|
||||
if deschedulerPodName != "" {
|
||||
printPodLogs(ctx, t, clientSet, deschedulerPodName)
|
||||
}
|
||||
|
||||
t.Logf("Deleting %q deployment...", deschedulerDeploymentObj.Name)
|
||||
err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(ctx, deschedulerDeploymentObj.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to delete %q deployment: %v", deschedulerDeploymentObj.Name, err)
|
||||
}
|
||||
waitForPodsToDisappear(ctx, t, clientSet, deschedulerDeploymentObj.Labels, deschedulerDeploymentObj.Namespace)
|
||||
}()
|
||||
|
||||
t.Logf("Waiting for the descheduler pod running")
|
||||
deschedulerPods := waitForPodsRunning(ctx, t, clientSet, deschedulerDeploymentObj.Labels, 1, deschedulerDeploymentObj.Namespace)
|
||||
if len(deschedulerPods) != 0 {
|
||||
deschedulerPodName = deschedulerPods[0].Name
|
||||
}
|
||||
createDeschedulerDeploymentWithCleanup(t, ctx, clientSet, deschedulerDeploymentObj)
|
||||
|
||||
// Run RemovePodsHavingTooManyRestarts strategy
|
||||
var meetsEvictedExpectations bool
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Used by the manifests GitHub Actions workflow (.github/workflows/manifests.yaml).
|
||||
# Three-node cluster without topology zone labels; not used by e2e tests.
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
nodes:
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2017 The Kubernetes 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.
|
||||
|
||||
e2e_repo_root() {
|
||||
local script_dir="$1"
|
||||
cd "${script_dir}/.." && pwd
|
||||
}
|
||||
|
||||
collect_logs() {
|
||||
local namespace pod
|
||||
echo "Collecting pods and logs"
|
||||
for namespace in "$@"; do
|
||||
kubectl get pods -n "${namespace}"
|
||||
for pod in $(kubectl get pods -n "${namespace}" -o name); do
|
||||
echo "Logs for ${pod}"
|
||||
kubectl logs -n "${namespace}" "${pod}"
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
kind_load_image() {
|
||||
local image="$1"
|
||||
if [ "${CONTAINER_ENGINE}" = "podman" ]; then
|
||||
local archive
|
||||
archive="/tmp/$(echo "${image}" | tr '/:' '_').tar"
|
||||
podman save "${image}" -o "${archive}"
|
||||
${KIND_SUDO} kind load image-archive "${archive}"
|
||||
rm "${archive}"
|
||||
else
|
||||
${KIND_SUDO} kind load docker-image "${image}"
|
||||
fi
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# E2E tool version defaults. Override via environment variables.
|
||||
# Keep .github/workflows/manifests.yaml matrix versions in sync with these values.
|
||||
|
||||
K8S_VERSION=${KUBERNETES_VERSION:-v1.36.1}
|
||||
KIND_VERSION=${KIND_VERSION:-v0.31.0}
|
||||
METRICS_SERVER_VERSION=${METRICS_SERVER_VERSION:-v0.8.1}
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2017 The Kubernetes 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.
|
||||
|
||||
kubectl create -f "https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-operator.yaml"
|
||||
kubectl create -f "https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-cr.yaml"
|
||||
# TODO(1.37): drop this patch when the k8s 1.34 e2e lane is removed. ImageVolume needs k8s >= 1.35.
|
||||
if [[ "${K8S_VERSION}" == v1.34* ]]; then
|
||||
kubectl -n kubevirt patch kubevirt kubevirt --type=merge --patch '{"spec":{"configuration":{"developerConfiguration":{"useEmulation":true,"disabledFeatureGates":["ImageVolume"]}}}}'
|
||||
else
|
||||
kubectl -n kubevirt patch kubevirt kubevirt --type=merge --patch '{"spec":{"configuration":{"developerConfiguration":{"useEmulation":true}}}}'
|
||||
fi
|
||||
kubectl wait --timeout=300s --for=condition=Available -n kubevirt kv/kubevirt
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2017 The Kubernetes 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.
|
||||
|
||||
kubectl apply -f "https://github.com/kubernetes-sigs/metrics-server/releases/download/${METRICS_SERVER_VERSION}/components.yaml"
|
||||
kubectl patch -n kube-system deployment metrics-server --type=json \
|
||||
-p '[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
|
||||
kubectl wait --timeout=180s --for=condition=Available -n kube-system deployment/metrics-server
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2017 The Kubernetes 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.
|
||||
|
||||
KIND_NODE_IMAGE=${KIND_NODE_IMAGE:-localhost/kindest/node:${K8S_VERSION}}
|
||||
|
||||
if [ -z "${SKIP_KUBECTL_INSTALL}" ]; then
|
||||
curl -Lo kubectl "https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/amd64/kubectl" && chmod +x kubectl && mv kubectl /usr/local/bin/
|
||||
fi
|
||||
if [ -z "${SKIP_KIND_INSTALL}" ]; then
|
||||
wget "https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/kind-linux-amd64"
|
||||
chmod +x kind-linux-amd64
|
||||
mv kind-linux-amd64 kind
|
||||
export PATH=$PATH:$PWD
|
||||
fi
|
||||
|
||||
if [ -z "${SKIP_INSTALL}" ]; then
|
||||
${KIND_SUDO} kind build node-image "${K8S_VERSION}" --image "${KIND_NODE_IMAGE}"
|
||||
${KIND_SUDO} kind create cluster --image "${KIND_NODE_IMAGE}" --config="${REPO_ROOT}/hack/kind_config.yaml"
|
||||
fi
|
||||
${CONTAINER_ENGINE} pull registry.k8s.io/pause
|
||||
kind_load_image registry.k8s.io/pause
|
||||
kind_load_image "${DESCHEDULER_IMAGE}"
|
||||
${KIND_SUDO} kind get kubeconfig > /tmp/admin.conf
|
||||
|
||||
export KUBECONFIG="/tmp/admin.conf"
|
||||
mkdir -p ~/gopath/src/sigs.k8s.io/
|
||||
+22
-64
@@ -18,92 +18,50 @@ set -x
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
|
||||
BASEDIR=$(dirname "$0")
|
||||
# shellcheck source=test/lib/e2e-common.sh
|
||||
source "${BASEDIR}/lib/e2e-common.sh"
|
||||
# shellcheck source=test/lib/e2e-versions.env
|
||||
source "${BASEDIR}/lib/e2e-versions.env"
|
||||
REPO_ROOT="$(e2e_repo_root "${BASEDIR}")"
|
||||
|
||||
# Set to empty if unbound/empty
|
||||
SKIP_INSTALL=${SKIP_INSTALL:-}
|
||||
KIND_E2E=${KIND_E2E:-}
|
||||
CONTAINER_ENGINE=${CONTAINER_ENGINE:-docker}
|
||||
KIND_SUDO=${KIND_SUDO:-}
|
||||
KIND_VERSION=${KIND_VERSION:-v0.31.0}
|
||||
SKIP_KUBECTL_INSTALL=${SKIP_KUBECTL_INSTALL:-}
|
||||
SKIP_KIND_INSTALL=${SKIP_KIND_INSTALL:-}
|
||||
SKIP_KUBEVIRT_INSTALL=${SKIP_KUBEVIRT_INSTALL:-}
|
||||
KUBEVIRT_VERSION=${KUBEVIRT_VERSION:-v1.6.2}
|
||||
SKIP_METRICS_SERVER_INSTALL=${SKIP_METRICS_SERVER_INSTALL:-}
|
||||
KUBEVIRT_VERSION=${KUBEVIRT_VERSION:-$(grep 'kubevirt.io/api ' "${REPO_ROOT}/go.mod" | awk '{print $2}')}
|
||||
|
||||
# Build a descheduler image
|
||||
IMAGE_TAG=v$(date +%Y%m%d)-$(git describe --tags)
|
||||
BASEDIR=$(dirname "$0")
|
||||
VERSION="${IMAGE_TAG}" make -C ${BASEDIR}/.. image
|
||||
VERSION="${IMAGE_TAG}" make -C "${REPO_ROOT}" image
|
||||
|
||||
export DESCHEDULER_IMAGE="docker.io/library/descheduler:${IMAGE_TAG}"
|
||||
echo "DESCHEDULER_IMAGE: ${DESCHEDULER_IMAGE}"
|
||||
|
||||
# This just runs e2e tests.
|
||||
if [ -n "$KIND_E2E" ]; then
|
||||
K8S_VERSION=${KUBERNETES_VERSION:-v1.35.1}
|
||||
if [ -z "${SKIP_KUBECTL_INSTALL}" ]; then
|
||||
curl -Lo kubectl https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/amd64/kubectl && chmod +x kubectl && mv kubectl /usr/local/bin/
|
||||
fi
|
||||
if [ -z "${SKIP_KIND_INSTALL}" ]; then
|
||||
wget https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/kind-linux-amd64
|
||||
chmod +x kind-linux-amd64
|
||||
mv kind-linux-amd64 kind
|
||||
export PATH=$PATH:$PWD
|
||||
fi
|
||||
|
||||
# If we did not set SKIP_INSTALL
|
||||
if [ -z "$SKIP_INSTALL" ]; then
|
||||
${KIND_SUDO} kind create cluster --image kindest/node:${K8S_VERSION} --config=./hack/kind_config.yaml
|
||||
fi
|
||||
${CONTAINER_ENGINE} pull registry.k8s.io/pause
|
||||
if [ "${CONTAINER_ENGINE}" == "podman" ]; then
|
||||
podman save registry.k8s.io/pause -o /tmp/pause.tar
|
||||
${KIND_SUDO} kind load image-archive /tmp/pause.tar
|
||||
rm /tmp/pause.tar
|
||||
podman save ${DESCHEDULER_IMAGE} -o /tmp/descheduler.tar
|
||||
${KIND_SUDO} kind load image-archive /tmp/descheduler.tar
|
||||
rm /tmp/descheduler.tar
|
||||
else
|
||||
${KIND_SUDO} kind load docker-image registry.k8s.io/pause
|
||||
${KIND_SUDO} kind load docker-image ${DESCHEDULER_IMAGE}
|
||||
fi
|
||||
${KIND_SUDO} kind get kubeconfig > /tmp/admin.conf
|
||||
|
||||
export KUBECONFIG="/tmp/admin.conf"
|
||||
mkdir -p ~/gopath/src/sigs.k8s.io/
|
||||
if [ -n "${KIND_E2E}" ]; then
|
||||
# shellcheck source=test/lib/setup-kind.sh
|
||||
source "${BASEDIR}/lib/setup-kind.sh"
|
||||
fi
|
||||
|
||||
# Deploy rbac, sa and binding for a descheduler running through a deployment
|
||||
kubectl apply -f kubernetes/base/rbac.yaml
|
||||
kubectl apply -f "${REPO_ROOT}/kubernetes/base/rbac.yaml"
|
||||
|
||||
collect_logs() {
|
||||
echo "Collecting pods and logs"
|
||||
kubectl get pods -n default
|
||||
kubectl get pods -n kubevirt
|
||||
|
||||
for pod in $(kubectl get pods -n default -o name); do
|
||||
echo "Logs for ${pod}"
|
||||
kubectl logs -n default ${pod}
|
||||
done
|
||||
|
||||
for pod in $(kubectl get pods -n kubevirt -o name); do
|
||||
echo "Logs for ${pod}"
|
||||
kubectl logs -n kubevirt ${pod}
|
||||
done
|
||||
}
|
||||
|
||||
trap "collect_logs" ERR
|
||||
trap 'collect_logs default kubevirt' ERR
|
||||
|
||||
if [ -z "${SKIP_KUBEVIRT_INSTALL}" ]; then
|
||||
kubectl create -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-operator.yaml
|
||||
kubectl create -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-cr.yaml
|
||||
kubectl wait --timeout=180s --for=condition=Available -n kubevirt kv/kubevirt
|
||||
kubectl -n kubevirt patch kubevirt kubevirt --type=merge --patch '{"spec":{"configuration":{"developerConfiguration":{"useEmulation":true}}}}'
|
||||
# shellcheck source=test/lib/install-kubevirt.sh
|
||||
source "${BASEDIR}/lib/install-kubevirt.sh"
|
||||
fi
|
||||
|
||||
METRICS_SERVER_VERSION="v0.8.1"
|
||||
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/${METRICS_SERVER_VERSION}/components.yaml
|
||||
kubectl patch -n kube-system deployment metrics-server --type=json \
|
||||
-p '[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
|
||||
if [ -z "${SKIP_METRICS_SERVER_INSTALL}" ]; then
|
||||
# shellcheck source=test/lib/install-metrics-server.sh
|
||||
source "${BASEDIR}/lib/install-metrics-server.sh"
|
||||
fi
|
||||
|
||||
PRJ_PREFIX="sigs.k8s.io/descheduler"
|
||||
go test ${PRJ_PREFIX}/test/e2e/ -v -timeout 0 --args --descheduler-image ${DESCHEDULER_IMAGE} --pod-run-as-user-id 1000 --pod-run-as-group-id 1000
|
||||
go test ${PRJ_PREFIX}/test/e2e/ -v -timeout 0 --args --descheduler-image "${DESCHEDULER_IMAGE}" --kubevirt-version-tag "${KUBEVIRT_VERSION}" --pod-run-as-user-id 1000 --pod-run-as-group-id 1000
|
||||
|
||||
Vendored
-1
@@ -16,7 +16,6 @@ go_library(
|
||||
importpath = "cel.dev/expr",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"@org_golang_google_genproto_googleapis_rpc//status:go_default_library",
|
||||
"@org_golang_google_protobuf//reflect/protoreflect",
|
||||
"@org_golang_google_protobuf//runtime/protoimpl",
|
||||
"@org_golang_google_protobuf//types/known/anypb",
|
||||
|
||||
Vendored
+2
-20
@@ -11,26 +11,9 @@ bazel_dep(
|
||||
version = "0.39.1",
|
||||
repo_name = "bazel_gazelle",
|
||||
)
|
||||
bazel_dep(
|
||||
name = "googleapis",
|
||||
version = "0.0.0-20241220-5e258e33.bcr.1",
|
||||
repo_name = "com_google_googleapis",
|
||||
)
|
||||
bazel_dep(
|
||||
name = "googleapis-cc",
|
||||
version = "1.0.0",
|
||||
)
|
||||
bazel_dep(
|
||||
name = "googleapis-java",
|
||||
version = "1.0.0",
|
||||
)
|
||||
bazel_dep(
|
||||
name = "googleapis-go",
|
||||
version = "1.0.0",
|
||||
)
|
||||
bazel_dep(
|
||||
name = "protobuf",
|
||||
version = "27.0",
|
||||
version = "27.1",
|
||||
repo_name = "com_google_protobuf",
|
||||
)
|
||||
bazel_dep(
|
||||
@@ -63,12 +46,11 @@ python.toolchain(
|
||||
)
|
||||
|
||||
go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk")
|
||||
go_sdk.download(version = "1.22.0")
|
||||
go_sdk.download(version = "1.23.0")
|
||||
|
||||
go_deps = use_extension("@bazel_gazelle//:extensions.bzl", "go_deps")
|
||||
go_deps.from_file(go_mod = "//:go.mod")
|
||||
use_repo(
|
||||
go_deps,
|
||||
"org_golang_google_genproto_googleapis_rpc",
|
||||
"org_golang_google_protobuf",
|
||||
)
|
||||
|
||||
+274
-475
File diff suppressed because it is too large
Load Diff
+29
-48
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.3
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v5.27.1
|
||||
// source: cel/expr/eval.proto
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
anypb "google.golang.org/protobuf/types/known/anypb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -373,58 +374,39 @@ func (x *EvalState_Result) GetValue() int64 {
|
||||
|
||||
var File_cel_expr_eval_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_cel_expr_eval_proto_rawDesc = []byte{
|
||||
0x0a, 0x13, 0x63, 0x65, 0x6c, 0x2f, 0x65, 0x78, 0x70, 0x72, 0x2f, 0x65, 0x76, 0x61, 0x6c, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x1a,
|
||||
0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x63, 0x65, 0x6c, 0x2f,
|
||||
0x65, 0x78, 0x70, 0x72, 0x2f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x22, 0xa2, 0x01, 0x0a, 0x09, 0x45, 0x76, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x2b,
|
||||
0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13,
|
||||
0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x07, 0x72,
|
||||
0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63,
|
||||
0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x45, 0x76, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74,
|
||||
0x65, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74,
|
||||
0x73, 0x1a, 0x32, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x65,
|
||||
0x78, 0x70, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x65, 0x78, 0x70, 0x72, 0x12,
|
||||
0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05,
|
||||
0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x9a, 0x01, 0x0a, 0x09, 0x45, 0x78, 0x70, 0x72, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x48, 0x00, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2a, 0x0a, 0x05,
|
||||
0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x65,
|
||||
0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x53, 0x65, 0x74, 0x48,
|
||||
0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x30, 0x0a, 0x07, 0x75, 0x6e, 0x6b, 0x6e,
|
||||
0x6f, 0x77, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x65, 0x6c, 0x2e,
|
||||
0x65, 0x78, 0x70, 0x72, 0x2e, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x53, 0x65, 0x74, 0x48,
|
||||
0x00, 0x52, 0x07, 0x75, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x42, 0x06, 0x0a, 0x04, 0x6b, 0x69,
|
||||
0x6e, 0x64, 0x22, 0x34, 0x0a, 0x08, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x53, 0x65, 0x74, 0x12, 0x28,
|
||||
0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10,
|
||||
0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||
0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x22, 0x66, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74,
|
||||
0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
|
||||
0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
||||
0x12, 0x2e, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
|
||||
0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73,
|
||||
0x22, 0x22, 0x0a, 0x0a, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x53, 0x65, 0x74, 0x12, 0x14,
|
||||
0x0a, 0x05, 0x65, 0x78, 0x70, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x05, 0x65,
|
||||
0x78, 0x70, 0x72, 0x73, 0x42, 0x2c, 0x0a, 0x0c, 0x64, 0x65, 0x76, 0x2e, 0x63, 0x65, 0x6c, 0x2e,
|
||||
0x65, 0x78, 0x70, 0x72, 0x42, 0x09, 0x45, 0x76, 0x61, 0x6c, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50,
|
||||
0x01, 0x5a, 0x0c, 0x63, 0x65, 0x6c, 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x65, 0x78, 0x70, 0x72, 0xf8,
|
||||
0x01, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
const file_cel_expr_eval_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x13cel/expr/eval.proto\x12\bcel.expr\x1a\x19google/protobuf/any.proto\x1a\x14cel/expr/value.proto\"\xa2\x01\n" +
|
||||
"\tEvalState\x12+\n" +
|
||||
"\x06values\x18\x01 \x03(\v2\x13.cel.expr.ExprValueR\x06values\x124\n" +
|
||||
"\aresults\x18\x03 \x03(\v2\x1a.cel.expr.EvalState.ResultR\aresults\x1a2\n" +
|
||||
"\x06Result\x12\x12\n" +
|
||||
"\x04expr\x18\x01 \x01(\x03R\x04expr\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\x03R\x05value\"\x9a\x01\n" +
|
||||
"\tExprValue\x12'\n" +
|
||||
"\x05value\x18\x01 \x01(\v2\x0f.cel.expr.ValueH\x00R\x05value\x12*\n" +
|
||||
"\x05error\x18\x02 \x01(\v2\x12.cel.expr.ErrorSetH\x00R\x05error\x120\n" +
|
||||
"\aunknown\x18\x03 \x01(\v2\x14.cel.expr.UnknownSetH\x00R\aunknownB\x06\n" +
|
||||
"\x04kind\"4\n" +
|
||||
"\bErrorSet\x12(\n" +
|
||||
"\x06errors\x18\x01 \x03(\v2\x10.cel.expr.StatusR\x06errors\"f\n" +
|
||||
"\x06Status\x12\x12\n" +
|
||||
"\x04code\x18\x01 \x01(\x05R\x04code\x12\x18\n" +
|
||||
"\amessage\x18\x02 \x01(\tR\amessage\x12.\n" +
|
||||
"\adetails\x18\x03 \x03(\v2\x14.google.protobuf.AnyR\adetails\"\"\n" +
|
||||
"\n" +
|
||||
"UnknownSet\x12\x14\n" +
|
||||
"\x05exprs\x18\x01 \x03(\x03R\x05exprsB,\n" +
|
||||
"\fdev.cel.exprB\tEvalProtoP\x01Z\fcel.dev/expr\xf8\x01\x01b\x06proto3"
|
||||
|
||||
var (
|
||||
file_cel_expr_eval_proto_rawDescOnce sync.Once
|
||||
file_cel_expr_eval_proto_rawDescData = file_cel_expr_eval_proto_rawDesc
|
||||
file_cel_expr_eval_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_cel_expr_eval_proto_rawDescGZIP() []byte {
|
||||
file_cel_expr_eval_proto_rawDescOnce.Do(func() {
|
||||
file_cel_expr_eval_proto_rawDescData = protoimpl.X.CompressGZIP(file_cel_expr_eval_proto_rawDescData)
|
||||
file_cel_expr_eval_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_cel_expr_eval_proto_rawDesc), len(file_cel_expr_eval_proto_rawDesc)))
|
||||
})
|
||||
return file_cel_expr_eval_proto_rawDescData
|
||||
}
|
||||
@@ -470,7 +452,7 @@ func file_cel_expr_eval_proto_init() {
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_cel_expr_eval_proto_rawDesc,
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_cel_expr_eval_proto_rawDesc), len(file_cel_expr_eval_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 6,
|
||||
NumExtensions: 0,
|
||||
@@ -481,7 +463,6 @@ func file_cel_expr_eval_proto_init() {
|
||||
MessageInfos: file_cel_expr_eval_proto_msgTypes,
|
||||
}.Build()
|
||||
File_cel_expr_eval_proto = out.File
|
||||
file_cel_expr_eval_proto_rawDesc = nil
|
||||
file_cel_expr_eval_proto_goTypes = nil
|
||||
file_cel_expr_eval_proto_depIdxs = nil
|
||||
}
|
||||
|
||||
+36
-77
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v3.21.5
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v5.27.1
|
||||
// source: cel/expr/explain.proto
|
||||
|
||||
package expr
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -20,23 +21,20 @@ const (
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// Deprecated: Do not use.
|
||||
// Deprecated: Marked as deprecated in cel/expr/explain.proto.
|
||||
type Explain struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Values []*Value `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
|
||||
ExprSteps []*Explain_ExprStep `protobuf:"bytes,2,rep,name=expr_steps,json=exprSteps,proto3" json:"expr_steps,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Values []*Value `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
|
||||
ExprSteps []*Explain_ExprStep `protobuf:"bytes,2,rep,name=expr_steps,json=exprSteps,proto3" json:"expr_steps,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Explain) Reset() {
|
||||
*x = Explain{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_cel_expr_explain_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_cel_expr_explain_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Explain) String() string {
|
||||
@@ -47,7 +45,7 @@ func (*Explain) ProtoMessage() {}
|
||||
|
||||
func (x *Explain) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_explain_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -77,21 +75,18 @@ func (x *Explain) GetExprSteps() []*Explain_ExprStep {
|
||||
}
|
||||
|
||||
type Explain_ExprStep struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
ValueIndex int32 `protobuf:"varint,2,opt,name=value_index,json=valueIndex,proto3" json:"value_index,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
ValueIndex int32 `protobuf:"varint,2,opt,name=value_index,json=valueIndex,proto3" json:"value_index,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Explain_ExprStep) Reset() {
|
||||
*x = Explain_ExprStep{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_cel_expr_explain_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_cel_expr_explain_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Explain_ExprStep) String() string {
|
||||
@@ -102,7 +97,7 @@ func (*Explain_ExprStep) ProtoMessage() {}
|
||||
|
||||
func (x *Explain_ExprStep) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_explain_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -133,42 +128,33 @@ func (x *Explain_ExprStep) GetValueIndex() int32 {
|
||||
|
||||
var File_cel_expr_explain_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_cel_expr_explain_proto_rawDesc = []byte{
|
||||
0x0a, 0x16, 0x63, 0x65, 0x6c, 0x2f, 0x65, 0x78, 0x70, 0x72, 0x2f, 0x65, 0x78, 0x70, 0x6c, 0x61,
|
||||
0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78,
|
||||
0x70, 0x72, 0x1a, 0x14, 0x63, 0x65, 0x6c, 0x2f, 0x65, 0x78, 0x70, 0x72, 0x2f, 0x76, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xae, 0x01, 0x0a, 0x07, 0x45, 0x78, 0x70,
|
||||
0x6c, 0x61, 0x69, 0x6e, 0x12, 0x27, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01,
|
||||
0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e,
|
||||
0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x39, 0x0a,
|
||||
0x0a, 0x65, 0x78, 0x70, 0x72, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
|
||||
0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x45, 0x78, 0x70,
|
||||
0x6c, 0x61, 0x69, 0x6e, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x53, 0x74, 0x65, 0x70, 0x52, 0x09, 0x65,
|
||||
0x78, 0x70, 0x72, 0x53, 0x74, 0x65, 0x70, 0x73, 0x1a, 0x3b, 0x0a, 0x08, 0x45, 0x78, 0x70, 0x72,
|
||||
0x53, 0x74, 0x65, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03,
|
||||
0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x69, 0x6e,
|
||||
0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x49, 0x6e, 0x64, 0x65, 0x78, 0x3a, 0x02, 0x18, 0x01, 0x42, 0x2f, 0x0a, 0x0c, 0x64, 0x65, 0x76,
|
||||
0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x42, 0x0c, 0x45, 0x78, 0x70, 0x6c, 0x61,
|
||||
0x69, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x0c, 0x63, 0x65, 0x6c, 0x2e, 0x64,
|
||||
0x65, 0x76, 0x2f, 0x65, 0x78, 0x70, 0x72, 0xf8, 0x01, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x33,
|
||||
}
|
||||
const file_cel_expr_explain_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x16cel/expr/explain.proto\x12\bcel.expr\x1a\x14cel/expr/value.proto\"\xae\x01\n" +
|
||||
"\aExplain\x12'\n" +
|
||||
"\x06values\x18\x01 \x03(\v2\x0f.cel.expr.ValueR\x06values\x129\n" +
|
||||
"\n" +
|
||||
"expr_steps\x18\x02 \x03(\v2\x1a.cel.expr.Explain.ExprStepR\texprSteps\x1a;\n" +
|
||||
"\bExprStep\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\x03R\x02id\x12\x1f\n" +
|
||||
"\vvalue_index\x18\x02 \x01(\x05R\n" +
|
||||
"valueIndex:\x02\x18\x01B/\n" +
|
||||
"\fdev.cel.exprB\fExplainProtoP\x01Z\fcel.dev/expr\xf8\x01\x01b\x06proto3"
|
||||
|
||||
var (
|
||||
file_cel_expr_explain_proto_rawDescOnce sync.Once
|
||||
file_cel_expr_explain_proto_rawDescData = file_cel_expr_explain_proto_rawDesc
|
||||
file_cel_expr_explain_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_cel_expr_explain_proto_rawDescGZIP() []byte {
|
||||
file_cel_expr_explain_proto_rawDescOnce.Do(func() {
|
||||
file_cel_expr_explain_proto_rawDescData = protoimpl.X.CompressGZIP(file_cel_expr_explain_proto_rawDescData)
|
||||
file_cel_expr_explain_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_cel_expr_explain_proto_rawDesc), len(file_cel_expr_explain_proto_rawDesc)))
|
||||
})
|
||||
return file_cel_expr_explain_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_cel_expr_explain_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_cel_expr_explain_proto_goTypes = []interface{}{
|
||||
var file_cel_expr_explain_proto_goTypes = []any{
|
||||
(*Explain)(nil), // 0: cel.expr.Explain
|
||||
(*Explain_ExprStep)(nil), // 1: cel.expr.Explain.ExprStep
|
||||
(*Value)(nil), // 2: cel.expr.Value
|
||||
@@ -189,37 +175,11 @@ func file_cel_expr_explain_proto_init() {
|
||||
return
|
||||
}
|
||||
file_cel_expr_value_proto_init()
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_cel_expr_explain_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Explain); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_cel_expr_explain_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Explain_ExprStep); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_cel_expr_explain_proto_rawDesc,
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_cel_expr_explain_proto_rawDesc), len(file_cel_expr_explain_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
@@ -230,7 +190,6 @@ func file_cel_expr_explain_proto_init() {
|
||||
MessageInfos: file_cel_expr_explain_proto_msgTypes,
|
||||
}.Build()
|
||||
File_cel_expr_explain_proto = out.File
|
||||
file_cel_expr_explain_proto_rawDesc = nil
|
||||
file_cel_expr_explain_proto_goTypes = nil
|
||||
file_cel_expr_explain_proto_depIdxs = nil
|
||||
}
|
||||
|
||||
+320
-559
File diff suppressed because it is too large
Load Diff
+135
-213
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v3.21.5
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v5.27.1
|
||||
// source: cel/expr/value.proto
|
||||
|
||||
package expr
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
structpb "google.golang.org/protobuf/types/known/structpb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,11 +24,8 @@ const (
|
||||
)
|
||||
|
||||
type Value struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Types that are assignable to Kind:
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Types that are valid to be assigned to Kind:
|
||||
//
|
||||
// *Value_NullValue
|
||||
// *Value_BoolValue
|
||||
@@ -41,16 +39,16 @@ type Value struct {
|
||||
// *Value_MapValue
|
||||
// *Value_ListValue
|
||||
// *Value_TypeValue
|
||||
Kind isValue_Kind `protobuf_oneof:"kind"`
|
||||
Kind isValue_Kind `protobuf_oneof:"kind"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Value) Reset() {
|
||||
*x = Value{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_cel_expr_value_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Value) String() string {
|
||||
@@ -61,7 +59,7 @@ func (*Value) ProtoMessage() {}
|
||||
|
||||
func (x *Value) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -76,93 +74,117 @@ func (*Value) Descriptor() ([]byte, []int) {
|
||||
return file_cel_expr_value_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (m *Value) GetKind() isValue_Kind {
|
||||
if m != nil {
|
||||
return m.Kind
|
||||
func (x *Value) GetKind() isValue_Kind {
|
||||
if x != nil {
|
||||
return x.Kind
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Value) GetNullValue() structpb.NullValue {
|
||||
if x, ok := x.GetKind().(*Value_NullValue); ok {
|
||||
return x.NullValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_NullValue); ok {
|
||||
return x.NullValue
|
||||
}
|
||||
}
|
||||
return structpb.NullValue(0)
|
||||
}
|
||||
|
||||
func (x *Value) GetBoolValue() bool {
|
||||
if x, ok := x.GetKind().(*Value_BoolValue); ok {
|
||||
return x.BoolValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_BoolValue); ok {
|
||||
return x.BoolValue
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Value) GetInt64Value() int64 {
|
||||
if x, ok := x.GetKind().(*Value_Int64Value); ok {
|
||||
return x.Int64Value
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_Int64Value); ok {
|
||||
return x.Int64Value
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Value) GetUint64Value() uint64 {
|
||||
if x, ok := x.GetKind().(*Value_Uint64Value); ok {
|
||||
return x.Uint64Value
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_Uint64Value); ok {
|
||||
return x.Uint64Value
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Value) GetDoubleValue() float64 {
|
||||
if x, ok := x.GetKind().(*Value_DoubleValue); ok {
|
||||
return x.DoubleValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_DoubleValue); ok {
|
||||
return x.DoubleValue
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Value) GetStringValue() string {
|
||||
if x, ok := x.GetKind().(*Value_StringValue); ok {
|
||||
return x.StringValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_StringValue); ok {
|
||||
return x.StringValue
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Value) GetBytesValue() []byte {
|
||||
if x, ok := x.GetKind().(*Value_BytesValue); ok {
|
||||
return x.BytesValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_BytesValue); ok {
|
||||
return x.BytesValue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Value) GetEnumValue() *EnumValue {
|
||||
if x, ok := x.GetKind().(*Value_EnumValue); ok {
|
||||
return x.EnumValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_EnumValue); ok {
|
||||
return x.EnumValue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Value) GetObjectValue() *anypb.Any {
|
||||
if x, ok := x.GetKind().(*Value_ObjectValue); ok {
|
||||
return x.ObjectValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_ObjectValue); ok {
|
||||
return x.ObjectValue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Value) GetMapValue() *MapValue {
|
||||
if x, ok := x.GetKind().(*Value_MapValue); ok {
|
||||
return x.MapValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_MapValue); ok {
|
||||
return x.MapValue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Value) GetListValue() *ListValue {
|
||||
if x, ok := x.GetKind().(*Value_ListValue); ok {
|
||||
return x.ListValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_ListValue); ok {
|
||||
return x.ListValue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Value) GetTypeValue() string {
|
||||
if x, ok := x.GetKind().(*Value_TypeValue); ok {
|
||||
return x.TypeValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_TypeValue); ok {
|
||||
return x.TypeValue
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -244,21 +266,18 @@ func (*Value_ListValue) isValue_Kind() {}
|
||||
func (*Value_TypeValue) isValue_Kind() {}
|
||||
|
||||
type EnumValue struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
|
||||
Value int32 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
|
||||
Value int32 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *EnumValue) Reset() {
|
||||
*x = EnumValue{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_cel_expr_value_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *EnumValue) String() string {
|
||||
@@ -269,7 +288,7 @@ func (*EnumValue) ProtoMessage() {}
|
||||
|
||||
func (x *EnumValue) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -299,20 +318,17 @@ func (x *EnumValue) GetValue() int32 {
|
||||
}
|
||||
|
||||
type ListValue struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Values []*Value `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Values []*Value `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ListValue) Reset() {
|
||||
*x = ListValue{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_cel_expr_value_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ListValue) String() string {
|
||||
@@ -323,7 +339,7 @@ func (*ListValue) ProtoMessage() {}
|
||||
|
||||
func (x *ListValue) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -346,20 +362,17 @@ func (x *ListValue) GetValues() []*Value {
|
||||
}
|
||||
|
||||
type MapValue struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Entries []*MapValue_Entry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Entries []*MapValue_Entry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *MapValue) Reset() {
|
||||
*x = MapValue{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_cel_expr_value_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *MapValue) String() string {
|
||||
@@ -370,7 +383,7 @@ func (*MapValue) ProtoMessage() {}
|
||||
|
||||
func (x *MapValue) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -393,21 +406,18 @@ func (x *MapValue) GetEntries() []*MapValue_Entry {
|
||||
}
|
||||
|
||||
type MapValue_Entry struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Key *Value `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
Value *Value `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Key *Value `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
Value *Value `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *MapValue_Entry) Reset() {
|
||||
*x = MapValue_Entry{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_cel_expr_value_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *MapValue_Entry) String() string {
|
||||
@@ -418,7 +428,7 @@ func (*MapValue_Entry) ProtoMessage() {}
|
||||
|
||||
func (x *MapValue_Entry) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[4]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -449,83 +459,58 @@ func (x *MapValue_Entry) GetValue() *Value {
|
||||
|
||||
var File_cel_expr_value_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_cel_expr_value_proto_rawDesc = []byte{
|
||||
0x0a, 0x14, 0x63, 0x65, 0x6c, 0x2f, 0x65, 0x78, 0x70, 0x72, 0x2f, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72,
|
||||
0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72,
|
||||
0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9d, 0x04, 0x0a, 0x05, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x12, 0x3b, 0x0a, 0x0a, 0x6e, 0x75, 0x6c, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x48, 0x00, 0x52, 0x09, 0x6e, 0x75, 0x6c, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x12, 0x1f, 0x0a, 0x0a, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x09, 0x62, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x12, 0x21, 0x0a, 0x0b, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x0a, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x56,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x12, 0x23, 0x0a, 0x0c, 0x75, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x5f, 0x76,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x0b, 0x75, 0x69,
|
||||
0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x23, 0x0a, 0x0c, 0x64, 0x6f, 0x75,
|
||||
0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x48,
|
||||
0x00, 0x52, 0x0b, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x23,
|
||||
0x0a, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06,
|
||||
0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x12, 0x21, 0x0a, 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x76, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65,
|
||||
0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x34, 0x0a, 0x0a, 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x76,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x65, 0x6c,
|
||||
0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x48,
|
||||
0x00, 0x52, 0x09, 0x65, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x39, 0x0a, 0x0c,
|
||||
0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0a, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x48, 0x00, 0x52, 0x0b, 0x6f, 0x62, 0x6a, 0x65,
|
||||
0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x31, 0x0a, 0x09, 0x6d, 0x61, 0x70, 0x5f, 0x76,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x65, 0x6c,
|
||||
0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x4d, 0x61, 0x70, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x48, 0x00,
|
||||
0x52, 0x08, 0x6d, 0x61, 0x70, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x34, 0x0a, 0x0a, 0x6c, 0x69,
|
||||
0x73, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13,
|
||||
0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x48, 0x00, 0x52, 0x09, 0x6c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x12, 0x1f, 0x0a, 0x0a, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0f,
|
||||
0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x74, 0x79, 0x70, 0x65, 0x56, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x42, 0x06, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0x35, 0x0a, 0x09, 0x45, 0x6e, 0x75,
|
||||
0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x22, 0x34, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x27, 0x0a,
|
||||
0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e,
|
||||
0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06,
|
||||
0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x91, 0x01, 0x0a, 0x08, 0x4d, 0x61, 0x70, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x12, 0x32, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01,
|
||||
0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e,
|
||||
0x4d, 0x61, 0x70, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07,
|
||||
0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x1a, 0x51, 0x0a, 0x05, 0x45, 0x6e, 0x74, 0x72, 0x79,
|
||||
0x12, 0x21, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e,
|
||||
0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03,
|
||||
0x6b, 0x65, 0x79, 0x12, 0x25, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x2d, 0x0a, 0x0c, 0x64, 0x65,
|
||||
0x76, 0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x42, 0x0a, 0x56, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x0c, 0x63, 0x65, 0x6c, 0x2e, 0x64, 0x65,
|
||||
0x76, 0x2f, 0x65, 0x78, 0x70, 0x72, 0xf8, 0x01, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x33,
|
||||
}
|
||||
const file_cel_expr_value_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x14cel/expr/value.proto\x12\bcel.expr\x1a\x19google/protobuf/any.proto\x1a\x1cgoogle/protobuf/struct.proto\"\x9d\x04\n" +
|
||||
"\x05Value\x12;\n" +
|
||||
"\n" +
|
||||
"null_value\x18\x01 \x01(\x0e2\x1a.google.protobuf.NullValueH\x00R\tnullValue\x12\x1f\n" +
|
||||
"\n" +
|
||||
"bool_value\x18\x02 \x01(\bH\x00R\tboolValue\x12!\n" +
|
||||
"\vint64_value\x18\x03 \x01(\x03H\x00R\n" +
|
||||
"int64Value\x12#\n" +
|
||||
"\fuint64_value\x18\x04 \x01(\x04H\x00R\vuint64Value\x12#\n" +
|
||||
"\fdouble_value\x18\x05 \x01(\x01H\x00R\vdoubleValue\x12#\n" +
|
||||
"\fstring_value\x18\x06 \x01(\tH\x00R\vstringValue\x12!\n" +
|
||||
"\vbytes_value\x18\a \x01(\fH\x00R\n" +
|
||||
"bytesValue\x124\n" +
|
||||
"\n" +
|
||||
"enum_value\x18\t \x01(\v2\x13.cel.expr.EnumValueH\x00R\tenumValue\x129\n" +
|
||||
"\fobject_value\x18\n" +
|
||||
" \x01(\v2\x14.google.protobuf.AnyH\x00R\vobjectValue\x121\n" +
|
||||
"\tmap_value\x18\v \x01(\v2\x12.cel.expr.MapValueH\x00R\bmapValue\x124\n" +
|
||||
"\n" +
|
||||
"list_value\x18\f \x01(\v2\x13.cel.expr.ListValueH\x00R\tlistValue\x12\x1f\n" +
|
||||
"\n" +
|
||||
"type_value\x18\x0f \x01(\tH\x00R\ttypeValueB\x06\n" +
|
||||
"\x04kind\"5\n" +
|
||||
"\tEnumValue\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\x05R\x05value\"4\n" +
|
||||
"\tListValue\x12'\n" +
|
||||
"\x06values\x18\x01 \x03(\v2\x0f.cel.expr.ValueR\x06values\"\x91\x01\n" +
|
||||
"\bMapValue\x122\n" +
|
||||
"\aentries\x18\x01 \x03(\v2\x18.cel.expr.MapValue.EntryR\aentries\x1aQ\n" +
|
||||
"\x05Entry\x12!\n" +
|
||||
"\x03key\x18\x01 \x01(\v2\x0f.cel.expr.ValueR\x03key\x12%\n" +
|
||||
"\x05value\x18\x02 \x01(\v2\x0f.cel.expr.ValueR\x05valueB-\n" +
|
||||
"\fdev.cel.exprB\n" +
|
||||
"ValueProtoP\x01Z\fcel.dev/expr\xf8\x01\x01b\x06proto3"
|
||||
|
||||
var (
|
||||
file_cel_expr_value_proto_rawDescOnce sync.Once
|
||||
file_cel_expr_value_proto_rawDescData = file_cel_expr_value_proto_rawDesc
|
||||
file_cel_expr_value_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_cel_expr_value_proto_rawDescGZIP() []byte {
|
||||
file_cel_expr_value_proto_rawDescOnce.Do(func() {
|
||||
file_cel_expr_value_proto_rawDescData = protoimpl.X.CompressGZIP(file_cel_expr_value_proto_rawDescData)
|
||||
file_cel_expr_value_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_cel_expr_value_proto_rawDesc), len(file_cel_expr_value_proto_rawDesc)))
|
||||
})
|
||||
return file_cel_expr_value_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_cel_expr_value_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||
var file_cel_expr_value_proto_goTypes = []interface{}{
|
||||
var file_cel_expr_value_proto_goTypes = []any{
|
||||
(*Value)(nil), // 0: cel.expr.Value
|
||||
(*EnumValue)(nil), // 1: cel.expr.EnumValue
|
||||
(*ListValue)(nil), // 2: cel.expr.ListValue
|
||||
@@ -556,69 +541,7 @@ func file_cel_expr_value_proto_init() {
|
||||
if File_cel_expr_value_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_cel_expr_value_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Value); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_cel_expr_value_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*EnumValue); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_cel_expr_value_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ListValue); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_cel_expr_value_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MapValue); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_cel_expr_value_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MapValue_Entry); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
file_cel_expr_value_proto_msgTypes[0].OneofWrappers = []interface{}{
|
||||
file_cel_expr_value_proto_msgTypes[0].OneofWrappers = []any{
|
||||
(*Value_NullValue)(nil),
|
||||
(*Value_BoolValue)(nil),
|
||||
(*Value_Int64Value)(nil),
|
||||
@@ -636,7 +559,7 @@ func file_cel_expr_value_proto_init() {
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_cel_expr_value_proto_rawDesc,
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_cel_expr_value_proto_rawDesc), len(file_cel_expr_value_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 5,
|
||||
NumExtensions: 0,
|
||||
@@ -647,7 +570,6 @@ func file_cel_expr_value_proto_init() {
|
||||
MessageInfos: file_cel_expr_value_proto_msgTypes,
|
||||
}.Build()
|
||||
File_cel_expr_value_proto = out.File
|
||||
file_cel_expr_value_proto_rawDesc = nil
|
||||
file_cel_expr_value_proto_goTypes = nil
|
||||
file_cel_expr_value_proto_depIdxs = nil
|
||||
}
|
||||
|
||||
+4
-4
@@ -17,9 +17,9 @@ ANTLR4 that it is compatible with (I.E. uses the /v4 path).
|
||||
However, this was found to be problematic, as it meant that with the runtime embedded so far underneath the root
|
||||
of the repo, the `go get` and related commands could not properly resolve the location of the go runtime source code.
|
||||
This meant that the reference to the runtime in your `go.mod` file would refer to the correct source code, but would not
|
||||
list the release tag such as @4.12.0 - this was confusing, to say the least.
|
||||
list the release tag such as @4.13.1 - this was confusing, to say the least.
|
||||
|
||||
As of 4.12.1, the runtime is now available as a go module in its own repo, and can be imported as `github.com/antlr4-go/antlr`
|
||||
As of 4.13.0, the runtime is now available as a go module in its own repo, and can be imported as `github.com/antlr4-go/antlr`
|
||||
(the go get command should also be used with this path). See the main documentation for the ANTLR4 project for more information,
|
||||
which is available at [ANTLR docs]. The documentation for using the Go runtime is available at [Go runtime docs].
|
||||
|
||||
@@ -49,7 +49,7 @@ Here is a general/recommended template for an ANTLR based recognizer in Go:
|
||||
.
|
||||
├── parser
|
||||
│ ├── mygrammar.g4
|
||||
│ ├── antlr-4.12.1-complete.jar
|
||||
│ ├── antlr-4.13.1-complete.jar
|
||||
│ ├── generate.go
|
||||
│ └── generate.sh
|
||||
├── parsing - generated code goes here
|
||||
@@ -71,7 +71,7 @@ And the generate.sh file will look similar to this:
|
||||
|
||||
#!/bin/sh
|
||||
|
||||
alias antlr4='java -Xmx500M -cp "./antlr4-4.12.1-complete.jar:$CLASSPATH" org.antlr.v4.Tool'
|
||||
alias antlr4='java -Xmx500M -cp "./antlr4-4.13.1-complete.jar:$CLASSPATH" org.antlr.v4.Tool'
|
||||
antlr4 -Dlanguage=Go -no-visitor -package parsing *.g4
|
||||
|
||||
depending on whether you want visitors or listeners or any other ANTLR options. Not that another option here
|
||||
|
||||
+3
-5
@@ -4,8 +4,6 @@
|
||||
|
||||
package antlr
|
||||
|
||||
import "sync"
|
||||
|
||||
// ATNInvalidAltNumber is used to represent an ALT number that has yet to be calculated or
|
||||
// which is invalid for a particular struct such as [*antlr.BaseRuleContext]
|
||||
var ATNInvalidAltNumber int
|
||||
@@ -56,9 +54,9 @@ type ATN struct {
|
||||
//
|
||||
states []ATNState
|
||||
|
||||
mu sync.Mutex
|
||||
stateMu sync.RWMutex
|
||||
edgeMu sync.RWMutex
|
||||
mu Mutex
|
||||
stateMu RWMutex
|
||||
edgeMu RWMutex
|
||||
}
|
||||
|
||||
// NewATN returns a new ATN struct representing the given grammarType and is used
|
||||
|
||||
-3
@@ -73,9 +73,6 @@ func NewATNConfig1(c *ATNConfig, state ATNState, context *PredictionContext) *AT
|
||||
// NewATNConfig creates a new ATNConfig instance given an existing config, a state, a context and a semantic context, other 'constructors'
|
||||
// are just wrappers around this one.
|
||||
func NewATNConfig(c *ATNConfig, state ATNState, context *PredictionContext, semanticContext SemanticContext) *ATNConfig {
|
||||
if semanticContext == nil {
|
||||
panic("semanticContext cannot be nil") // TODO: Remove this - probably put here for some bug that is now fixed
|
||||
}
|
||||
b := &ATNConfig{}
|
||||
b.InitATNConfig(c, state, c.GetAlt(), context, semanticContext)
|
||||
b.cType = parserConfig
|
||||
|
||||
+1
-1
@@ -148,7 +148,7 @@ func (is *InputStream) GetTextFromInterval(i Interval) string {
|
||||
}
|
||||
|
||||
func (*InputStream) GetSourceName() string {
|
||||
return ""
|
||||
return "Obtained from string"
|
||||
}
|
||||
|
||||
// String returns the entire input stream as a string
|
||||
|
||||
+2
-3
@@ -8,7 +8,6 @@ import (
|
||||
"container/list"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Collectable is an interface that a struct should implement if it is to be
|
||||
@@ -587,12 +586,12 @@ type VisitRecord struct {
|
||||
|
||||
type VisitList struct {
|
||||
cache *list.List
|
||||
lock sync.RWMutex
|
||||
lock RWMutex
|
||||
}
|
||||
|
||||
var visitListPool = VisitList{
|
||||
cache: list.New(),
|
||||
lock: sync.RWMutex{},
|
||||
lock: RWMutex{},
|
||||
}
|
||||
|
||||
// NewVisitRecord returns a new VisitRecord instance from the pool if available.
|
||||
|
||||
+1
-1
@@ -207,7 +207,7 @@ func (b *BaseLexer) NextToken() Token {
|
||||
for {
|
||||
b.thetype = TokenInvalidType
|
||||
|
||||
ttype := b.safeMatch()
|
||||
ttype := b.safeMatch() // Defaults to LexerSkip
|
||||
|
||||
if b.input.LA(1) == TokenEOF {
|
||||
b.hitEOF = true
|
||||
|
||||
+1
@@ -40,6 +40,7 @@ func (la *LL1Analyzer) getDecisionLookahead(s ATNState) []*IntervalSet {
|
||||
for alt := 0; alt < count; alt++ {
|
||||
|
||||
look[alt] = NewIntervalSet()
|
||||
// TODO: This is one of the reasons that ATNConfigs are allocated and freed all the time - fix this tomorrow jim!
|
||||
lookBusy := NewJStore[*ATNConfig, Comparator[*ATNConfig]](aConfEqInst, ClosureBusyCollection, "LL1Analyzer.getDecisionLookahead for lookBusy")
|
||||
la.look1(s.GetTransitions()[alt].getTarget(), nil, BasePredictionContextEMPTY, look[alt], lookBusy, NewBitSet(), false, false)
|
||||
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
//go:build !antlr.nomutex
|
||||
// +build !antlr.nomutex
|
||||
|
||||
package antlr
|
||||
|
||||
import "sync"
|
||||
|
||||
// Mutex is a simple mutex implementation which just delegates to sync.Mutex, it
|
||||
// is used to provide a mutex implementation for the antlr package, which users
|
||||
// can turn off with the build tag -tags antlr.nomutex
|
||||
type Mutex struct {
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (m *Mutex) Lock() {
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
func (m *Mutex) Unlock() {
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
type RWMutex struct {
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (m *RWMutex) Lock() {
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
func (m *RWMutex) Unlock() {
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
func (m *RWMutex) RLock() {
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
func (m *RWMutex) RUnlock() {
|
||||
m.mu.RUnlock()
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
//go:build antlr.nomutex
|
||||
// +build antlr.nomutex
|
||||
|
||||
package antlr
|
||||
|
||||
type Mutex struct{}
|
||||
|
||||
func (m *Mutex) Lock() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
func (m *Mutex) Unlock() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
type RWMutex struct{}
|
||||
|
||||
func (m *RWMutex) Lock() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
func (m *RWMutex) Unlock() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
func (m *RWMutex) RLock() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
func (m *RWMutex) RUnlock() {
|
||||
// No-op
|
||||
}
|
||||
+1
-3
@@ -10,8 +10,6 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ()
|
||||
|
||||
// ClosureBusy is a store of ATNConfigs and is a tiny abstraction layer over
|
||||
// a standard JStore so that we can use Lazy instantiation of the JStore, mostly
|
||||
// to avoid polluting the stats module with a ton of JStore instances with nothing in them.
|
||||
@@ -883,7 +881,7 @@ func (p *ParserATNSimulator) getPredicatePredictions(ambigAlts *BitSet, altToPre
|
||||
// the ERROR state was reached, outerContext as the initial parser context from the paper
|
||||
// or the parser stack at the instant before prediction commences.
|
||||
//
|
||||
// Teh func returns the value to return from [AdaptivePredict], or
|
||||
// The func returns the value to return from [AdaptivePredict], or
|
||||
// [ATNInvalidAltNumber] if a suitable alternative was not
|
||||
// identified and [AdaptivePredict] should report an error instead.
|
||||
func (p *ParserATNSimulator) getSynValidOrSemInvalidAltThatFinishedDecisionEntryRule(configs *ATNConfigSet, outerContext ParserRuleContext) int {
|
||||
|
||||
+30
-30
@@ -6,7 +6,6 @@ package antlr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/exp/slices"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
@@ -101,7 +100,7 @@ func NewArrayPredictionContext(parents []*PredictionContext, returnStates []int)
|
||||
hash = murmurUpdate(hash, returnState)
|
||||
}
|
||||
hash = murmurFinish(hash, len(parents)<<1)
|
||||
|
||||
|
||||
nec := &PredictionContext{}
|
||||
nec.cachedHash = hash
|
||||
nec.pcType = PredictionContextArray
|
||||
@@ -115,6 +114,9 @@ func (p *PredictionContext) Hash() int {
|
||||
}
|
||||
|
||||
func (p *PredictionContext) Equals(other Collectable[*PredictionContext]) bool {
|
||||
if p == other {
|
||||
return true
|
||||
}
|
||||
switch p.pcType {
|
||||
case PredictionContextEmpty:
|
||||
otherP := other.(*PredictionContext)
|
||||
@@ -138,13 +140,11 @@ func (p *PredictionContext) ArrayEquals(o Collectable[*PredictionContext]) bool
|
||||
if p.cachedHash != other.Hash() {
|
||||
return false // can't be same if hash is different
|
||||
}
|
||||
|
||||
|
||||
// Must compare the actual array elements and not just the array address
|
||||
//
|
||||
return slices.Equal(p.returnStates, other.returnStates) &&
|
||||
slices.EqualFunc(p.parents, other.parents, func(x, y *PredictionContext) bool {
|
||||
return x.Equals(y)
|
||||
})
|
||||
return intSlicesEqual(p.returnStates, other.returnStates) &&
|
||||
pcSliceEqual(p.parents, other.parents)
|
||||
}
|
||||
|
||||
func (p *PredictionContext) SingletonEquals(other Collectable[*PredictionContext]) bool {
|
||||
@@ -152,23 +152,23 @@ func (p *PredictionContext) SingletonEquals(other Collectable[*PredictionContext
|
||||
return false
|
||||
}
|
||||
otherP := other.(*PredictionContext)
|
||||
if otherP == nil {
|
||||
if otherP == nil || otherP.pcType != PredictionContextSingleton {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
if p.cachedHash != otherP.Hash() {
|
||||
return false // Can't be same if hash is different
|
||||
}
|
||||
|
||||
|
||||
if p.returnState != otherP.getReturnState(0) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// Both parents must be nil if one is
|
||||
if p.parentCtx == nil {
|
||||
return otherP.parentCtx == nil
|
||||
}
|
||||
|
||||
|
||||
return p.parentCtx.Equals(otherP.parentCtx)
|
||||
}
|
||||
|
||||
@@ -225,27 +225,27 @@ func (p *PredictionContext) String() string {
|
||||
return "$"
|
||||
case PredictionContextSingleton:
|
||||
var up string
|
||||
|
||||
|
||||
if p.parentCtx == nil {
|
||||
up = ""
|
||||
} else {
|
||||
up = p.parentCtx.String()
|
||||
}
|
||||
|
||||
|
||||
if len(up) == 0 {
|
||||
if p.returnState == BasePredictionContextEmptyReturnState {
|
||||
return "$"
|
||||
}
|
||||
|
||||
|
||||
return strconv.Itoa(p.returnState)
|
||||
}
|
||||
|
||||
|
||||
return strconv.Itoa(p.returnState) + " " + up
|
||||
case PredictionContextArray:
|
||||
if p.isEmpty() {
|
||||
return "[]"
|
||||
}
|
||||
|
||||
|
||||
s := "["
|
||||
for i := 0; i < len(p.returnStates); i++ {
|
||||
if i > 0 {
|
||||
@@ -263,7 +263,7 @@ func (p *PredictionContext) String() string {
|
||||
}
|
||||
}
|
||||
return s + "]"
|
||||
|
||||
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
@@ -309,18 +309,18 @@ func predictionContextFromRuleContext(a *ATN, outerContext RuleContext) *Predict
|
||||
parent := predictionContextFromRuleContext(a, outerContext.GetParent().(RuleContext))
|
||||
state := a.states[outerContext.GetInvokingState()]
|
||||
transition := state.GetTransitions()[0]
|
||||
|
||||
|
||||
return SingletonBasePredictionContextCreate(parent, transition.(*RuleTransition).followState.GetStateNumber())
|
||||
}
|
||||
|
||||
func merge(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMap) *PredictionContext {
|
||||
|
||||
|
||||
// Share same graph if both same
|
||||
//
|
||||
if a == b || a.Equals(b) {
|
||||
return a
|
||||
}
|
||||
|
||||
|
||||
if a.pcType == PredictionContextSingleton && b.pcType == PredictionContextSingleton {
|
||||
return mergeSingletons(a, b, rootIsWildcard, mergeCache)
|
||||
}
|
||||
@@ -334,7 +334,7 @@ func merge(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMap) *Pr
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Convert either Singleton or Empty to arrays, so that we can merge them
|
||||
//
|
||||
ara := convertToArray(a)
|
||||
@@ -395,7 +395,7 @@ func mergeSingletons(a, b *PredictionContext, rootIsWildcard bool, mergeCache *J
|
||||
return previous
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
rootMerge := mergeRoot(a, b, rootIsWildcard)
|
||||
if rootMerge != nil {
|
||||
if mergeCache != nil {
|
||||
@@ -564,7 +564,7 @@ func mergeArrays(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMa
|
||||
i := 0 // walks a
|
||||
j := 0 // walks b
|
||||
k := 0 // walks target M array
|
||||
|
||||
|
||||
mergedReturnStates := make([]int, len(a.returnStates)+len(b.returnStates))
|
||||
mergedParents := make([]*PredictionContext, len(a.returnStates)+len(b.returnStates))
|
||||
// walk and merge to yield mergedParents, mergedReturnStates
|
||||
@@ -626,9 +626,9 @@ func mergeArrays(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMa
|
||||
mergedParents = mergedParents[0:k]
|
||||
mergedReturnStates = mergedReturnStates[0:k]
|
||||
}
|
||||
|
||||
|
||||
M := NewArrayPredictionContext(mergedParents, mergedReturnStates)
|
||||
|
||||
|
||||
// if we created same array as a or b, return that instead
|
||||
// TODO: JI track whether this is possible above during merge sort for speed and possibly avoid an allocation
|
||||
if M.Equals(a) {
|
||||
@@ -650,7 +650,7 @@ func mergeArrays(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMa
|
||||
return b
|
||||
}
|
||||
combineCommonParents(&mergedParents)
|
||||
|
||||
|
||||
if mergeCache != nil {
|
||||
mergeCache.Put(a, b, M)
|
||||
}
|
||||
@@ -666,7 +666,7 @@ func mergeArrays(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMa
|
||||
//goland:noinspection GoUnusedFunction
|
||||
func combineCommonParents(parents *[]*PredictionContext) {
|
||||
uniqueParents := NewJStore[*PredictionContext, Comparator[*PredictionContext]](pContextEqInst, PredictionContextCollection, "combineCommonParents for PredictionContext")
|
||||
|
||||
|
||||
for p := 0; p < len(*parents); p++ {
|
||||
parent := (*parents)[p]
|
||||
_, _ = uniqueParents.Put(parent)
|
||||
@@ -685,7 +685,7 @@ func getCachedBasePredictionContext(context *PredictionContext, contextCache *Pr
|
||||
if present {
|
||||
return existing
|
||||
}
|
||||
|
||||
|
||||
existing, present = contextCache.Get(context)
|
||||
if present {
|
||||
visited.Put(context, existing)
|
||||
@@ -722,6 +722,6 @@ func getCachedBasePredictionContext(context *PredictionContext, contextCache *Pr
|
||||
contextCache.add(updated)
|
||||
visited.Put(updated, updated)
|
||||
visited.Put(context, updated)
|
||||
|
||||
|
||||
return updated
|
||||
}
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ var tokenTypeMapCache = make(map[string]int)
|
||||
var ruleIndexMapCache = make(map[string]int)
|
||||
|
||||
func (b *BaseRecognizer) checkVersion(toolVersion string) {
|
||||
runtimeVersion := "4.12.0"
|
||||
runtimeVersion := "4.13.1"
|
||||
if runtimeVersion != toolVersion {
|
||||
fmt.Println("ANTLR runtime and generated code versions disagree: " + runtimeVersion + "!=" + toolVersion)
|
||||
}
|
||||
|
||||
+1
-2
@@ -9,7 +9,6 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// This file allows the user to collect statistics about the runtime of the ANTLR runtime. It is not enabled by default
|
||||
@@ -30,7 +29,7 @@ type goRunStats struct {
|
||||
// within this package.
|
||||
//
|
||||
jStats []*JStatRec
|
||||
jStatsLock sync.RWMutex
|
||||
jStatsLock RWMutex
|
||||
topN int
|
||||
topNByMax []*JStatRec
|
||||
topNByUsed []*JStatRec
|
||||
|
||||
+41
-41
@@ -104,6 +104,25 @@ func (b *BaseToken) GetSource() *TokenSourceCharStreamPair {
|
||||
return b.source
|
||||
}
|
||||
|
||||
func (b *BaseToken) GetText() string {
|
||||
if b.text != "" {
|
||||
return b.text
|
||||
}
|
||||
input := b.GetInputStream()
|
||||
if input == nil {
|
||||
return ""
|
||||
}
|
||||
n := input.Size()
|
||||
if b.GetStart() < n && b.GetStop() < n {
|
||||
return input.GetTextFromInterval(NewInterval(b.GetStart(), b.GetStop()))
|
||||
}
|
||||
return "<EOF>"
|
||||
}
|
||||
|
||||
func (b *BaseToken) SetText(text string) {
|
||||
b.text = text
|
||||
}
|
||||
|
||||
func (b *BaseToken) GetTokenIndex() int {
|
||||
return b.tokenIndex
|
||||
}
|
||||
@@ -120,6 +139,28 @@ func (b *BaseToken) GetInputStream() CharStream {
|
||||
return b.source.charStream
|
||||
}
|
||||
|
||||
func (b *BaseToken) String() string {
|
||||
txt := b.GetText()
|
||||
if txt != "" {
|
||||
txt = strings.Replace(txt, "\n", "\\n", -1)
|
||||
txt = strings.Replace(txt, "\r", "\\r", -1)
|
||||
txt = strings.Replace(txt, "\t", "\\t", -1)
|
||||
} else {
|
||||
txt = "<no text>"
|
||||
}
|
||||
|
||||
var ch string
|
||||
if b.GetChannel() > 0 {
|
||||
ch = ",channel=" + strconv.Itoa(b.GetChannel())
|
||||
} else {
|
||||
ch = ""
|
||||
}
|
||||
|
||||
return "[@" + strconv.Itoa(b.GetTokenIndex()) + "," + strconv.Itoa(b.GetStart()) + ":" + strconv.Itoa(b.GetStop()) + "='" +
|
||||
txt + "',<" + strconv.Itoa(b.GetTokenType()) + ">" +
|
||||
ch + "," + strconv.Itoa(b.GetLine()) + ":" + strconv.Itoa(b.GetColumn()) + "]"
|
||||
}
|
||||
|
||||
type CommonToken struct {
|
||||
BaseToken
|
||||
}
|
||||
@@ -170,44 +211,3 @@ func (c *CommonToken) clone() *CommonToken {
|
||||
t.text = c.GetText()
|
||||
return t
|
||||
}
|
||||
|
||||
func (c *CommonToken) GetText() string {
|
||||
if c.text != "" {
|
||||
return c.text
|
||||
}
|
||||
input := c.GetInputStream()
|
||||
if input == nil {
|
||||
return ""
|
||||
}
|
||||
n := input.Size()
|
||||
if c.start < n && c.stop < n {
|
||||
return input.GetTextFromInterval(NewInterval(c.start, c.stop))
|
||||
}
|
||||
return "<EOF>"
|
||||
}
|
||||
|
||||
func (c *CommonToken) SetText(text string) {
|
||||
c.text = text
|
||||
}
|
||||
|
||||
func (c *CommonToken) String() string {
|
||||
txt := c.GetText()
|
||||
if txt != "" {
|
||||
txt = strings.Replace(txt, "\n", "\\n", -1)
|
||||
txt = strings.Replace(txt, "\r", "\\r", -1)
|
||||
txt = strings.Replace(txt, "\t", "\\t", -1)
|
||||
} else {
|
||||
txt = "<no text>"
|
||||
}
|
||||
|
||||
var ch string
|
||||
if c.channel > 0 {
|
||||
ch = ",channel=" + strconv.Itoa(c.channel)
|
||||
} else {
|
||||
ch = ""
|
||||
}
|
||||
|
||||
return "[@" + strconv.Itoa(c.tokenIndex) + "," + strconv.Itoa(c.start) + ":" + strconv.Itoa(c.stop) + "='" +
|
||||
txt + "',<" + strconv.Itoa(c.tokenType) + ">" +
|
||||
ch + "," + strconv.Itoa(c.line) + ":" + strconv.Itoa(c.column) + "]"
|
||||
}
|
||||
|
||||
+53
@@ -326,3 +326,56 @@ func isDirectory(dir string) (bool, error) {
|
||||
}
|
||||
return fileInfo.IsDir(), err
|
||||
}
|
||||
|
||||
// intSlicesEqual returns true if the two slices of ints are equal, and is a little
|
||||
// faster than slices.Equal.
|
||||
func intSlicesEqual(s1, s2 []int) bool {
|
||||
if s1 == nil && s2 == nil {
|
||||
return true
|
||||
}
|
||||
if s1 == nil || s2 == nil {
|
||||
return false
|
||||
}
|
||||
if len(s1) == 0 && len(s2) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(s1) == 0 || len(s2) == 0 || len(s1) != len(s2) {
|
||||
return false
|
||||
}
|
||||
// If the slices are using the same memory, then they are the same slice
|
||||
if &s1[0] == &s2[0] {
|
||||
return true
|
||||
}
|
||||
for i, v := range s1 {
|
||||
if v != s2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func pcSliceEqual(s1, s2 []*PredictionContext) bool {
|
||||
if s1 == nil && s2 == nil {
|
||||
return true
|
||||
}
|
||||
if s1 == nil || s2 == nil {
|
||||
return false
|
||||
}
|
||||
if len(s1) == 0 && len(s2) == 0 {
|
||||
return true
|
||||
}
|
||||
if len(s1) == 0 || len(s2) == 0 || len(s1) != len(s2) {
|
||||
return false
|
||||
}
|
||||
// If the slices are using the same memory, then they are the same slice
|
||||
if &s1[0] == &s2[0] {
|
||||
return true
|
||||
}
|
||||
for i, v := range s1 {
|
||||
if !v.Equals(s2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
Generated
Vendored
+6
-17
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
// Copyright 2025
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -12,22 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package swag
|
||||
//go:build !unix
|
||||
|
||||
import "mime/multipart"
|
||||
package daemon
|
||||
|
||||
// File represents an uploaded file.
|
||||
type File struct {
|
||||
Data multipart.File
|
||||
Header *multipart.FileHeader
|
||||
}
|
||||
|
||||
// Read bytes from the file
|
||||
func (f *File) Read(p []byte) (n int, err error) {
|
||||
return f.Data.Read(p)
|
||||
}
|
||||
|
||||
// Close the file
|
||||
func (f *File) Close() error {
|
||||
return f.Data.Close()
|
||||
// SdNotifyMonotonicUsec returns the empty string on unsupported platforms.
|
||||
func SdNotifyMonotonicUsec() string {
|
||||
return ""
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Copyright 2025
|
||||
//
|
||||
// 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.
|
||||
|
||||
//go:build unix
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// SdNotifyMonotonicUsec returns a MONOTONIC_USEC=... assignment for the current time
|
||||
// with a trailing newline included. This is typically used with [SdNotifyReloading].
|
||||
//
|
||||
// If the monotonic clock is not available on the system, the empty string is returned.
|
||||
func SdNotifyMonotonicUsec() string {
|
||||
var ts unix.Timespec
|
||||
if err := unix.ClockGettime(unix.CLOCK_MONOTONIC, &ts); err != nil {
|
||||
// Monotonic clock is not available on this system.
|
||||
return ""
|
||||
}
|
||||
return "MONOTONIC_USEC=" + strconv.FormatInt(ts.Nano()/1000, 10) + "\n"
|
||||
}
|
||||
+4
-3
@@ -15,6 +15,7 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -51,10 +52,10 @@ func SdWatchdogEnabled(unsetEnvironment bool) (time.Duration, error) {
|
||||
}
|
||||
s, err := strconv.Atoi(wusec)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("error converting WATCHDOG_USEC: %s", err)
|
||||
return 0, fmt.Errorf("error converting WATCHDOG_USEC: %w", err)
|
||||
}
|
||||
if s <= 0 {
|
||||
return 0, fmt.Errorf("error WATCHDOG_USEC must be a positive number")
|
||||
return 0, errors.New("error WATCHDOG_USEC must be a positive number")
|
||||
}
|
||||
interval := time.Duration(s) * time.Microsecond
|
||||
|
||||
@@ -63,7 +64,7 @@ func SdWatchdogEnabled(unsetEnvironment bool) (time.Duration, error) {
|
||||
}
|
||||
p, err := strconv.Atoi(wpid)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("error converting WATCHDOG_PID: %s", err)
|
||||
return 0, fmt.Errorf("error converting WATCHDOG_PID: %w", err)
|
||||
}
|
||||
if os.Getpid() != p {
|
||||
return 0, nil
|
||||
|
||||
+1
-1
@@ -41,6 +41,6 @@ const (
|
||||
)
|
||||
|
||||
// Print prints a message to the local systemd journal using Send().
|
||||
func Print(priority Priority, format string, a ...interface{}) error {
|
||||
func Print(priority Priority, format string, a ...any) error {
|
||||
return Send(fmt.Sprintf(format, a...), priority, nil)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user